/*
 * Copyright 1999-2017 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.parser;

import me.itsoo.elasticbatis.parsing.druid.DbType;
import me.itsoo.elasticbatis.parsing.druid.sql.SQLUtils;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.SQLCommentHint;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.SQLDeclareItem;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.SQLExpr;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.SQLName;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.SQLOrderBy;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.SQLStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.SQLStatementImpl;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.expr.SQLBinaryOpExpr;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.expr.SQLBinaryOperator;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.expr.SQLCharExpr;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.expr.SQLHexExpr;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.expr.SQLIdentifierExpr;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.expr.SQLIntegerExpr;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.expr.SQLLiteralExpr;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.expr.SQLPropertyExpr;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.expr.SQLQueryExpr;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.expr.SQLVariantRefExpr;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLArchiveTableStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLAssignItem;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLBackupStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLBlockStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLBuildTableStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLCancelJobStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLColumnDefinition;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLCommitStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLCopyFromStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLExportDatabaseStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLExportTableStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLExprStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLExprTableSource;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLIfStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLImportDatabaseStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLImportTableStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLInsertStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLLoopStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLReplaceStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLRestoreStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLRollbackStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLSelect;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLSelectStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLSetStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLStartTransactionStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLSubmitJobStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLSyncMetaStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLTableSource;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLUpdateSetItem;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLUpdateStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLWhileStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.clause.ConditionValue;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.clause.MySqlCaseStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.clause.MySqlCursorDeclareStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.clause.MySqlDeclareConditionStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.clause.MySqlDeclareHandlerStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.clause.MySqlDeclareStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.clause.MySqlHandlerType;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.clause.MySqlLeaveStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.clause.MySqlRepeatStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.CobarShowStatus;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.DrdsBaselineStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.DrdsCancelDDLJob;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.DrdsChangeDDLJob;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.DrdsClearDDLJobCache;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.DrdsInspectDDLJobCache;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.DrdsRecoverDDLJob;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.DrdsRemoveDDLJob;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.DrdsRollbackDDLJob;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.MySqlAnalyzeStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.MySqlCheckTableStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.MySqlChecksumTableStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.MySqlClearPlanCacheStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.MySqlDeleteStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.MySqlDisabledPlanCacheStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.MySqlExecuteForAdsStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.MySqlExecuteStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.MySqlExplainStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.MySqlFlashbackStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.MySqlFlushStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.MySqlHelpStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.MySqlHintStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.MySqlInsertStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.MySqlLoadDataInFileStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.MySqlLoadXmlStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.MySqlLockTableStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.MySqlManageInstanceGroupStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.MySqlMigrateStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.MySqlOptimizeStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.MySqlPrepareStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.MySqlRaftLeaderTransferStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.MySqlRaftMemberChangeStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.MySqlResetStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.MySqlSetTransactionStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.MySqlUnlockTablesStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.MySqlUpdateStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.ast.statement.MysqlDeallocatePrepareStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.parser.EOFParserException;
import me.itsoo.elasticbatis.parsing.druid.sql.parser.InsertColumnsCache;
import me.itsoo.elasticbatis.parsing.druid.sql.parser.Lexer;
import me.itsoo.elasticbatis.parsing.druid.sql.parser.ParserException;
import me.itsoo.elasticbatis.parsing.druid.sql.parser.SQLParserFeature;
import me.itsoo.elasticbatis.parsing.druid.sql.parser.SQLStatementParser;
import me.itsoo.elasticbatis.parsing.druid.sql.parser.Token;
import me.itsoo.elasticbatis.parsing.druid.sql.repository.SchemaObject;
import me.itsoo.elasticbatis.parsing.druid.sql.visitor.SQLASTOutputVisitor;
import me.itsoo.elasticbatis.parsing.druid.util.FnvHash;
import me.itsoo.elasticbatis.parsing.druid.util.StringUtils;

import java.util.ArrayList;
import java.util.List;

public class MySqlStatementParser extends SQLStatementParser {

    private static final String CHAIN = "CHAIN";
    private static final String GLOBAL = "GLOBAL";
    private static final String STATUS = "STATUS";
    private static final String RESET = "RESET";
    private static final String DESCRIBE = "DESCRIBE";
    private static final String WRITE = "WRITE";
    private static final String READ = "READ";
    private static final String LOCAL = "LOCAL";
    private static final String TABLES = "TABLES";
    private static final String CONNECTION = "CONNECTION";

    public MySqlStatementParser(String sql) {
        super(new MySqlExprParser(sql));
    }

    public MySqlStatementParser(String sql, SQLParserFeature... features) {
        super(new MySqlExprParser(sql, features));
    }

    public SQLStatement parseSelect() {
        MySqlSelectParser selectParser = createSQLSelectParser();
        SQLSelect select = selectParser.select();
        if (selectParser.returningFlag) {
            return selectParser.updateStmt;
        }
        return new SQLSelectStatement(select, DbType.mysql);
    }

    public SQLUpdateStatement parseUpdateStatement() {
        return new MySqlSelectParser(this.exprParser, selectListCache).parseUpdateStatment();
    }

    protected MySqlUpdateStatement createUpdateStatement() {
        return new MySqlUpdateStatement();
    }

    public MySqlDeleteStatement parseDeleteStatement() {
        MySqlDeleteStatement deleteStatement = new MySqlDeleteStatement();
        if (lexer.isKeepComments() && lexer.hasComment()) {
            List<String> comments = lexer.readAndResetComments();

            if (comments != null) {
                deleteStatement.addBeforeComment(comments);
            }
        }
        if (lexer.token() == Token.DELETE) {
            lexer.nextToken();
            if (lexer.token() == Token.COMMENT) {
                lexer.nextToken();
            }
            if (lexer.token() == Token.HINT) {
                this.getExprParser().parseHints(deleteStatement.getHints());
            }
            if (lexer.identifierEquals(FnvHash.Constants.LOW_PRIORITY)) {
                deleteStatement.setLowPriority(true);
                lexer.nextToken();
            }
            if (lexer.identifierEquals("QUICK")) {
                deleteStatement.setQuick(true);
                lexer.nextToken();
            }
            if (lexer.identifierEquals(FnvHash.Constants.IGNORE)) {
                deleteStatement.setIgnore(true);
                lexer.nextToken();
            }
            if (lexer.identifierEquals(FnvHash.Constants.FORCE)) {
                Lexer.SavePoint savePoint = lexer.mark();
                lexer.nextToken();
                if (lexer.token() == Token.ALL) {
                    lexer.nextToken();
                    acceptIdentifier("PARTITIONS");
                    deleteStatement.setForceAllPartitions(true);
                } else if (lexer.identifierEquals(FnvHash.Constants.PARTITIONS)) {
                    lexer.nextToken();
                    deleteStatement.setForceAllPartitions(true);
                } else if (lexer.token() == Token.PARTITION) {
                    lexer.nextToken();
                    SQLName partition = this.exprParser.name();
                    deleteStatement.setForcePartition(partition);
                } else {
                    lexer.reset(savePoint);
                }
            }
            if (lexer.token() == Token.IDENTIFIER) {
                deleteStatement.setTableSource(createSQLSelectParser().parseTableSource());
                if (lexer.token() == Token.FROM) {
                    lexer.nextToken();
                    SQLTableSource tableSource = createSQLSelectParser().parseTableSource();
                    deleteStatement.setFrom(tableSource);
                }
            } else if (lexer.token() == Token.FROM) {
                lexer.nextToken();
                if (lexer.token() == Token.FULLTEXT) {
                    lexer.nextToken();
                    if (lexer.identifierEquals(FnvHash.Constants.DICTIONARY)) {
                        lexer.nextToken();
                        deleteStatement.setFulltextDictionary(true);
                    }
                }
                deleteStatement.setTableSource(createSQLSelectParser().parseTableSource());
            } else {
                throw new ParserException("syntax error. " + lexer.info());
            }
            if (lexer.identifierEquals(FnvHash.Constants.USING)) {
                lexer.nextToken();
                SQLTableSource tableSource = createSQLSelectParser().parseTableSource();
                deleteStatement.setUsing(tableSource);
            }
        }
        if (lexer.token() == (Token.WHERE)) {
            lexer.nextToken();
            SQLExpr where = this.exprParser.expr();
            deleteStatement.setWhere(where);
        }
        if (lexer.token() == (Token.ORDER)) {
            SQLOrderBy orderBy = exprParser.parseOrderBy();
            deleteStatement.setOrderBy(orderBy);
        }
        if (lexer.token() == Token.LIMIT) {
            deleteStatement.setLimit(this.exprParser.parseLimit());
        }
        if (lexer.token() != Token.EOF && lexer.token() != Token.SEMI) {
            throw new ParserException("syntax error. " + lexer.info());
        }
        return deleteStatement;
    }

    public MySqlAnalyzeStatement parseAnalyze() {
        accept(Token.ANALYZE);
        MySqlAnalyzeStatement stmt = new MySqlAnalyzeStatement();
        if (lexer.token() == Token.TABLE) {
            accept(Token.TABLE);
            List<SQLName> names = new ArrayList<SQLName>();
            this.exprParser.names(names, stmt);

            for (SQLName name : names) {
                stmt.addTableSource(new SQLExprTableSource(name));
            }
            if (lexer.token() == Token.WHERE) {
                accept(Token.WHERE);
                SQLExpr where = this.exprParser.expr();
                stmt.setAdbWhere(where);
            }
        } else if (lexer.token() == Token.DATABASE) {
            accept(Token.DATABASE);
            SQLName name = this.exprParser.name();
            stmt.setAdbSchema((SQLIdentifierExpr) name);
        } else if (lexer.token() == Token.COLUMN) {
            accept(Token.COLUMN);
            SQLName table = this.exprParser.name();
            stmt.setTable(table);
            accept(Token.LPAREN);
            for (; ; ) {
                SQLName name = this.exprParser.name();
                stmt.getAdbColumns().add((SQLIdentifierExpr) name);
                if (lexer.token() == Token.COMMA) {
                    accept(Token.COMMA);
                    continue;
                }
                break;
            }
            accept(Token.RPAREN);
            if (lexer.token() == Token.WHERE) {
                accept(Token.WHERE);
                SQLExpr where = this.exprParser.expr();
                stmt.setAdbWhere(where);
            }
        } else if (lexer.identifierEquals("columns")) {
            lexer.nextToken();
            accept(Token.GROUP);
            SQLName table = this.exprParser.name();
            stmt.setTable(table);
            accept(Token.LPAREN);
            for (; ; ) {
                SQLName name = this.exprParser.name();
                stmt.getAdbColumnsGroup().add((SQLIdentifierExpr) name);
                if (lexer.token() == Token.COMMA) {
                    accept(Token.COMMA);
                    continue;
                }
                break;
            }
            accept(Token.RPAREN);
            if (lexer.token() == Token.WHERE) {
                accept(Token.WHERE);
                SQLExpr where = this.exprParser.expr();
                stmt.setAdbWhere(where);
            }
        }
        if (lexer.token() == Token.PARTITION) {
            stmt.setPartition(parsePartitionRef());
        }
        if (lexer.token() == Token.COMPUTE) {
            lexer.nextToken();
            acceptIdentifier("STATISTICS");
            stmt.setComputeStatistics(true);
        }
        if (lexer.token() == Token.FOR) {
            lexer.nextToken();
            acceptIdentifier("COLUMNS");
            stmt.setForColums(true);
        }
        if (lexer.identifierEquals(FnvHash.Constants.CACHE)) {
            lexer.nextToken();
            acceptIdentifier("METADATA");
            stmt.setCacheMetadata(true);
        }
        if (lexer.identifierEquals(FnvHash.Constants.NOSCAN)) {
            lexer.nextToken();
            stmt.setNoscan(true);
        }
        return stmt;
    }

    public MySqlOptimizeStatement parseOptimize() {
        accept(Token.OPTIMIZE);
        accept(Token.TABLE);
        MySqlOptimizeStatement stmt = new MySqlOptimizeStatement();
        List<SQLName> names = new ArrayList<SQLName>();
        this.exprParser.names(names, stmt);
        for (SQLName name : names) {
            stmt.addTableSource(new SQLExprTableSource(name));
        }
        return stmt;
    }

    public SQLStatement parseReset() {
        acceptIdentifier(RESET);
        MySqlResetStatement stmt = new MySqlResetStatement();
        for (; ; ) {
            if (lexer.token() == Token.IDENTIFIER) {
                if (lexer.identifierEquals("QUERY")) {
                    lexer.nextToken();
                    accept(Token.CACHE);
                    stmt.getOptions().add("QUERY CACHE");
                } else {
                    stmt.getOptions().add(lexer.stringVal());
                    lexer.nextToken();
                }

                if (lexer.token() == Token.COMMA) {
                    lexer.nextToken();
                    continue;
                }
            }
            break;
        }
        return stmt;
    }

    public boolean parseStatementListDialect(List<SQLStatement> statementList) {
        if (lexer.identifierEquals("PREPARE")) {
            MySqlPrepareStatement stmt = parsePrepare();
            statementList.add(stmt);
            return true;
        }
        if (lexer.identifierEquals("EXECUTE")) {
            acceptIdentifier("EXECUTE");
            if (lexer.identifierEquals("RESTART") || lexer.identifierEquals("UPDATE")) {
                MySqlExecuteForAdsStatement stmt = parseExecuteForAds();
                statementList.add(stmt);
            } else {
                MySqlExecuteStatement stmt = parseExecute();
                statementList.add(stmt);
            }
            return true;
        }
        if (lexer.identifierEquals("DEALLOCATE")) {
            MysqlDeallocatePrepareStatement stmt = parseDeallocatePrepare();
            statementList.add(stmt);
            return true;
        }
        if (lexer.identifierEquals("LOAD")) {
            SQLStatement stmt = parseLoad();
            statementList.add(stmt);
            return true;
        }
        if (lexer.token() == Token.REPLACE) {
            SQLReplaceStatement stmt = parseReplace();
            statementList.add(stmt);
            return true;
        }
        if (lexer.identifierEquals("START")) {
            SQLStartTransactionStatement stmt = parseStart();
            statementList.add(stmt);
            return true;
        }
        if (lexer.token() == Token.SHOW) {
            SQLStatement stmt = parseShow();
            statementList.add(stmt);
            return true;
        }
        if (lexer.identifierEquals("CLEAR")) {
            lexer.nextToken();
            if (isEnabled(SQLParserFeature.DRDSAsyncDDL) && lexer.identifierEquals("DDL")) {
                // CLEAR DDL CACHE { ALL | <job_id> [ , <job_id> ] ... }
                lexer.nextToken();
                accept(Token.CACHE);
                DrdsClearDDLJobCache stmt = new DrdsClearDDLJobCache();
                if (Token.ALL == lexer.token()) {
                    lexer.nextToken();
                    stmt.setAllJobs(true);
                    statementList.add(stmt);
                    return true;
                } else {
                    while (true) {
                        stmt.addJobId(lexer.integerValue().longValue());
                        accept(Token.LITERAL_INT);
                        if (Token.COMMA == lexer.token()) {
                            lexer.nextToken();
                        } else if (lexer.token() == Token.EOF || lexer.token() == Token.SEMI) {
                            break;
                        } else {
                            throw new ParserException("syntax error, expect job id, actual " + lexer.token() + ", " + lexer.info());
                        }
                    }
                    statementList.add(stmt);
                    return true;
                }
            }
            acceptIdentifier("PLANCACHE");
            statementList.add(new MySqlClearPlanCacheStatement());
            return true;
        }
        if (lexer.identifierEquals("DISABLED")) {
            lexer.nextToken();
            acceptIdentifier("PLANCACHE");
            statementList.add(new MySqlDisabledPlanCacheStatement());
            return true;
        }
        if (lexer.token() == Token.EXPLAIN) {
            SQLStatement stmt = this.parseExplain();
            statementList.add(stmt);
            return true;
        }
        if (lexer.identifierEquals(RESET)) {
            SQLStatement stmt = parseReset();
            statementList.add(stmt);
            return true;
        }
        if (lexer.token() == Token.ANALYZE) {
            SQLStatement stmt = parseAnalyze();
            statementList.add(stmt);
            return true;
        }
        if (lexer.identifierEquals(FnvHash.Constants.ARCHIVE)) {
            SQLStatement stmt = parseArchive();
            statementList.add(stmt);
            return true;
        }
        if (lexer.identifierEquals(FnvHash.Constants.BACKUP)) {
            SQLStatement stmt = parseBackup();
            statementList.add(stmt);
            return true;
        }
        if (lexer.identifierEquals(FnvHash.Constants.RESTORE)) {
            SQLStatement stmt = parseRestore();
            statementList.add(stmt);
            return true;
        }
        if (lexer.identifierEquals("BUILD")) {
            SQLStatement stmt = parseBuildTable();
            statementList.add(stmt);
            return true;
        }
        if (lexer.identifierEquals("CANCEL")) {
            SQLStatement stmt = parseCancelJob();
            statementList.add(stmt);
            return true;
        }
        if (lexer.identifierEquals(FnvHash.Constants.EXPORT)) {
            lexer.nextToken();
            if (lexer.token() == Token.TABLE) {
                SQLStatement stmt = parseExportTable();
                statementList.add(stmt);
            } else if (lexer.token() == Token.DATABASE) {
                SQLStatement stmt = parseExportDB();
                statementList.add(stmt);
            }
            return true;
        }
        if (lexer.identifierEquals(FnvHash.Constants.IMPORT)) {
            lexer.nextToken();
            if (lexer.token() == Token.TABLE) {
                SQLStatement stmt = parseImportTable();
                statementList.add(stmt);
            } else if (lexer.token() == Token.DATABASE) {
                SQLStatement stmt = parseImportDB();
                statementList.add(stmt);
            }
            return true;
        }
        if (lexer.identifierEquals("SUBMIT")) {
            lexer.nextToken();
            acceptIdentifier("JOB");
            SQLStatement stmt = parseSubmitJob();
            statementList.add(stmt);
            return true;
        }
        if (lexer.identifierEquals(FnvHash.Constants.MIGRATE)) {
            SQLStatement stmt = parseMigrate();
            statementList.add(stmt);
            return true;
        }
        if (lexer.token() == Token.OPTIMIZE) {
            SQLStatement stmt = parseOptimize();
            statementList.add(stmt);
            return true;
        }
        if (lexer.identifierEquals("HELP")) {
            lexer.nextToken();
            MySqlHelpStatement stmt = new MySqlHelpStatement();
            stmt.setContent(this.exprParser.primary());
            statementList.add(stmt);
            return true;
        }
        if (lexer.identifierEquals("FLUSH")) {
            SQLStatement stmt = parseFlush();
            statementList.add(stmt);
            return true;
        }
        if (lexer.identifierEquals(FnvHash.Constants.SYNC)) {
            SQLStatement stmt = parseSync();
            statementList.add(stmt);
            return true;
        }
        if (lexer.identifierEquals(FnvHash.Constants.INIT)) {
            statementList.add(new SQLExprStatement(this.exprParser.expr()));
            return true;
        }
        // DRDS async DDL.
        if (isEnabled(SQLParserFeature.DRDSAsyncDDL) && lexer.identifierEquals(FnvHash.Constants.RECOVER)) {
            // RECOVER DDL {ALL | <job_id> [, <job_id>] ...}
            lexer.nextToken();
            acceptIdentifier("DDL");
            DrdsRecoverDDLJob stmt = new DrdsRecoverDDLJob();
            if (Token.ALL == lexer.token()) {
                lexer.nextToken();
                stmt.setAllJobs(true);
                statementList.add(stmt);
                return true;
            } else {
                while (true) {
                    stmt.addJobId(lexer.integerValue().longValue());
                    accept(Token.LITERAL_INT);
                    if (Token.COMMA == lexer.token()) {
                        lexer.nextToken();
                    } else if (lexer.token() == Token.EOF || lexer.token() == Token.SEMI) {
                        break;
                    } else {
                        throw new ParserException("syntax error, expect job id, actual " + lexer.token() + ", " + lexer.info());
                    }
                }
                statementList.add(stmt);
                return true;
            }
        }
        if (isEnabled(SQLParserFeature.DRDSAsyncDDL) && lexer.identifierEquals(FnvHash.Constants.REMOVE)) {
            // REMOVE DDL { ALL { COMPLETED | PENDING } | <job_id> [, <job_id>] ...}
            lexer.nextToken();
            acceptIdentifier("DDL");
            DrdsRemoveDDLJob stmt = new DrdsRemoveDDLJob();
            if (Token.ALL == lexer.token()) {
                lexer.nextToken();
                if (lexer.identifierEquals("COMPLETED")) {
                    lexer.nextToken();
                    stmt.setAllCompleted(true);
                } else if (lexer.identifierEquals("PENDING")) {
                    lexer.nextToken();
                    stmt.setAllPending(true);
                } else {
                    throw new ParserException("syntax error, expect COMPLETED or PENDING, actual " + lexer.token() + ", " + lexer.info());
                }
            } else {
                while (true) {
                    stmt.addJobId(lexer.integerValue().longValue());
                    accept(Token.LITERAL_INT);
                    if (Token.COMMA == lexer.token()) {
                        lexer.nextToken();
                    } else if (lexer.token() == Token.EOF || lexer.token() == Token.SEMI) {
                        break;
                    } else {
                        throw new ParserException("syntax error, expect job id, actual " + lexer.token() + ", " + lexer.info());
                    }
                }
            }
            statementList.add(stmt);
            return true;
        }
        if (isEnabled(SQLParserFeature.DRDSAsyncDDL) && lexer.identifierEquals("INSPECT")) {
            // INSPECT DDL CACHE
            lexer.nextToken();
            acceptIdentifier("DDL");
            accept(Token.CACHE);
            statementList.add(new DrdsInspectDDLJobCache());
            return true;
        }
        if (isEnabled(SQLParserFeature.DRDSAsyncDDL) && lexer.identifierEquals(FnvHash.Constants.CHANGE)) {
            // CHANGE DDL <job_id> { SKIP | ADD } <group_and_table_name> [ , <group_and_table_name> ] ...
            Lexer.SavePoint mark = lexer.mark();
            lexer.nextToken();
            if (lexer.identifierEquals("DDL")) {
                lexer.nextToken();
                DrdsChangeDDLJob stmt = new DrdsChangeDDLJob();
                stmt.setJobId(lexer.integerValue().longValue());
                accept(Token.LITERAL_INT);
                if (lexer.identifierEquals("SKIP")) {
                    lexer.nextToken();
                    stmt.setSkip(true);
                } else if (lexer.identifierEquals("ADD")) {
                    lexer.nextToken();
                    stmt.setAdd(true);
                } else {
                    throw new ParserException("syntax error, expect SKIP or ADD, actual " + lexer.token() + ", " + lexer.info());
                }
                StringBuilder builder = new StringBuilder();
                while (true) {
                    if (Token.COMMA == lexer.token()) {
                        lexer.nextToken();
                        stmt.addGroupAndTableNameList(builder.toString());
                        builder = new StringBuilder();
                    } else if (lexer.token() == Token.EOF || lexer.token() == Token.SEMI) {
                        stmt.addGroupAndTableNameList(builder.toString());
                        break;
                    } else if (lexer.token() == Token.COLON) {
                        builder.append(':');
                        lexer.nextToken();
                    } else if (lexer.token() == Token.DOT) {
                        builder.append('.');
                        lexer.nextToken();
                    } else {
                        builder.append(lexer.stringVal());
                        lexer.nextToken();
                    }
                }
                statementList.add(stmt);
                return true;
            }
            lexer.reset(mark);
        }
        if (isEnabled(SQLParserFeature.DRDSBaseline) && lexer.identifierEquals("BASELINE")) {
            lexer.nextToken();
            DrdsBaselineStatement stmt = new DrdsBaselineStatement();
            if (Token.EOF == lexer.token() || Token.SEMI == lexer.token() ||
                    lexer.stringVal().isEmpty() || lexer.stringVal().equalsIgnoreCase("BASELINE")) {
                throw new ParserException("syntax error, expect baseline operation, actual " + lexer.token() + ", " + lexer.info());
            }
            stmt.setOperation(lexer.stringVal());
            lexer.setToken(Token.COMMA); // Hack here: Set previous comma to deal with negative number.
            lexer.nextToken();
            if (lexer.identifierEquals(FnvHash.Constants.SQL)) {
                // Parse select.
                lexer.nextToken();
                if (lexer.token() == Token.HINT) {
                    stmt.setHeadHints(this.exprParser.parseHints());
                }
                MySqlSelectParser selectParser = createSQLSelectParser();
                stmt.setSelect(selectParser.select());
            } else {
                // Parse id list.
                while (lexer.token() != Token.EOF && lexer.token() != Token.SEMI) {
                    stmt.addBaselineId(lexer.integerValue().longValue());
                    accept(Token.LITERAL_INT);
                    if (Token.COMMA == lexer.token()) {
                        lexer.nextToken();
                    }
                }
            }
            statementList.add(stmt);
            return true;
        }
        if (lexer.token() == Token.DESC || lexer.identifierEquals(DESCRIBE)) {
            SQLStatement stmt = parseDescribe();
            statementList.add(stmt);
            return true;
        }
        if (lexer.token() == Token.LOCK) {
            lexer.nextToken();
            String val = lexer.stringVal();
            boolean isLockTables = TABLES.equalsIgnoreCase(val) && lexer.token() == Token.IDENTIFIER;
            boolean isLockTable = "TABLE".equalsIgnoreCase(val) && lexer.token() == Token.TABLE;
            if (isLockTables || isLockTable) {
                lexer.nextToken();
            } else {
                setErrorEndPos(lexer.pos());
                throw new ParserException("syntax error, expect TABLES or TABLE, actual " + lexer.token() + ", " + lexer.info());
            }
            MySqlLockTableStatement stmt = new MySqlLockTableStatement();
            for (; ; ) {
                MySqlLockTableStatement.Item item = new MySqlLockTableStatement.Item();
                SQLExprTableSource tableSource = null;
                SQLName tableName = this.exprParser.name();
                if (lexer.token() == Token.AS) {
                    lexer.nextToken();
                    String as = lexer.stringVal();
                    tableSource = new SQLExprTableSource(tableName, as);
                    lexer.nextToken();
                } else {
                    tableSource = new SQLExprTableSource(tableName);
                }
                item.setTableSource(tableSource);
                stmt.getItems().add(item);
                if (lexer.token() == Token.COMMA) {
                    lexer.nextToken();
                    continue;
                }
                if (lexer.identifierEquals(READ)) {
                    lexer.nextToken();
                    if (lexer.identifierEquals(LOCAL)) {
                        lexer.nextToken();
                        item.setLockType(MySqlLockTableStatement.LockType.READ_LOCAL);
                    } else {
                        item.setLockType(MySqlLockTableStatement.LockType.READ);
                    }
                } else if (lexer.identifierEquals(WRITE)) {
                    lexer.nextToken();
                    item.setLockType(MySqlLockTableStatement.LockType.WRITE);
                } else if (lexer.identifierEquals(FnvHash.Constants.LOW_PRIORITY)) {
                    lexer.nextToken();
                    acceptIdentifier(WRITE);
                    lexer.nextToken();
                    item.setLockType(MySqlLockTableStatement.LockType.LOW_PRIORITY_WRITE);
                } else {
                    throw new ParserException(
                            "syntax error, expect READ or WRITE OR AS, actual " + lexer.token() + ", " + lexer.info());
                }
                if (lexer.token() == Token.HINT) {
                    item.setHints(this.exprParser.parseHints());
                }
                if (lexer.token() == Token.COMMA) {
                    lexer.nextToken();
                    continue;
                }
                break;
            }
            statementList.add(stmt);
            return true;
        }
        if (lexer.identifierEquals("UNLOCK")) {
            lexer.nextToken();
            String val = lexer.stringVal();
            boolean isUnLockTables = TABLES.equalsIgnoreCase(val) && lexer.token() == Token.IDENTIFIER;
            boolean isUnLockTable = "TABLE".equalsIgnoreCase(val) && lexer.token() == Token.TABLE;
            statementList.add(new MySqlUnlockTablesStatement());
            if (isUnLockTables || isUnLockTable) {
                lexer.nextToken();
            } else {
                setErrorEndPos(lexer.pos());
                throw new ParserException("syntax error, expect TABLES or TABLE, actual " + lexer.token() + ", " + lexer.info());
            }
            return true;
        }
        if (lexer.identifierEquals(FnvHash.Constants.CHECKSUM)) {
            statementList.add(this.parseChecksum());
            return true;
        }
        if (lexer.token() == Token.HINT) {
            List<SQLCommentHint> hints = this.exprParser.parseHints();
            boolean tddlHints = false;
            boolean accept = false;
            boolean acceptHint = false;
            switch (lexer.token()) {
                case SELECT:
                case WITH:
                case DELETE:
                case UPDATE:
                case INSERT:
                case SHOW:
                case REPLACE:
                case TRUNCATE:
                case DROP:
                case ALTER:
                case CREATE:
                case CHECK:
                case SET:
                case DESC:
                case OPTIMIZE:
                case ANALYZE:
                case KILL:
                case EXPLAIN:
                case LPAREN:
                    acceptHint = true;
                    break;
                case IDENTIFIER:
                    acceptHint = lexer.hash_lower() == FnvHash.Constants.DUMP
                            || lexer.hash_lower() == FnvHash.Constants.RENAME
                            || lexer.hash_lower() == FnvHash.Constants.DESCRIBE;
                    break;
                default:
                    break;
            }
            if (hints.size() >= 1
                    && statementList.size() == 0
                    && acceptHint) {
                SQLCommentHint hint = hints.get(0);
                String hintText = hint.getText().toUpperCase();
                if (hintText.startsWith("+TDDL")
                        || hintText.startsWith("+ TDDL")
                        || hintText.startsWith("TDDL")
                        || hintText.startsWith("!TDDL")) {
                    tddlHints = true;
                } else if (hintText.startsWith("+")) {
                    accept = true;
                }
            }
            if (tddlHints) {
                SQLStatementImpl stmt = (SQLStatementImpl) this.parseStatement();
                stmt.setHeadHints(hints);
                statementList.add(stmt);
                return true;
            } else if (accept) {
                SQLStatementImpl stmt = (SQLStatementImpl) this.parseStatement();
                stmt.setHeadHints(hints);
                statementList.add(stmt);
                return true;
            }
            MySqlHintStatement stmt = new MySqlHintStatement();
            stmt.setHints(hints);
            statementList.add(stmt);
            return true;
        }
        if (lexer.token() == Token.BEGIN) {
            statementList.add(this.parseBlock());
            return true;
        }
        if (lexer.identifierEquals(FnvHash.Constants.ADD)) {
            statementList.add(parseAddManageInstanceGroup());
            return true;
        }
        if (lexer.token() == Token.IDENTIFIER) {
            String label = lexer.stringVal();
            char ch = lexer.current();
            int bp = lexer.bp();
            lexer.nextToken();
            if (lexer.token() == Token.VARIANT && lexer.stringVal().equals(":")) {
                lexer.nextToken();
                if (lexer.token() == Token.LOOP) {
                    // parse loop statement
                    statementList.add(this.parseLoop(label));
                } else if (lexer.token() == Token.WHILE) {
                    // parse while statement with label
                    statementList.add(this.parseWhile(label));
                } else if (lexer.token() == Token.BEGIN) {
                    // parse begin-end statement with label
                    SQLBlockStatement block = this.parseBlock(label);
                    statementList.add(block);
                } else if (lexer.token() == Token.REPEAT) {
                    // parse repeat statement with label
                    statementList.add(this.parseRepeat(label));
                }
                return true;
            } else {
                lexer.reset(bp, ch, Token.IDENTIFIER);
            }
        }
        if (lexer.token() == Token.CHECK) {
            final Lexer.SavePoint mark = lexer.mark();
            lexer.nextToken();
            if (lexer.token() == Token.TABLE) {
                lexer.nextToken();
                MySqlCheckTableStatement stmt = new MySqlCheckTableStatement();
                for (; ; ) {
                    SQLName table = this.exprParser.name();
                    stmt.addTable(new SQLExprTableSource(table));
                    if (lexer.token() == Token.COMMA) {
                        lexer.nextToken();
                        continue;
                    }
                    break;
                }
                statementList.add(stmt);
            }
            return true;
        }
        return false;
    }

    private SQLStatement parseArchive() {
        lexer.nextToken();
        accept(Token.TABLE);
        SQLArchiveTableStatement stmt = new SQLArchiveTableStatement();
        SQLName tableName = this.exprParser.name();
        stmt.setTable(tableName);
        stmt.setType(new SQLIdentifierExpr("UPLOAD"));
        if (lexer.token() == Token.LITERAL_INT) {
            for (; ; ) {
                stmt.getSpIdList().add(this.exprParser.integerExpr());
                String pidStr = lexer.stringVal();
                accept(Token.VARIANT);
                String s = pidStr.replaceAll(":", "");
                if (StringUtils.isEmpty(s)) {
                    stmt.getpIdList().add(exprParser.integerExpr());
                } else {
                    stmt.getpIdList().add(new SQLIntegerExpr(Integer.valueOf(s)));
                }
                if (lexer.token() == Token.COMMA) {
                    lexer.nextToken();
                    continue;
                }
                break;
            }
        }
        return stmt;
    }

    private SQLStatement parseBackup() {
        lexer.nextToken();
        SQLBackupStatement stmt = new SQLBackupStatement();
        String type = "BACKUP_DATA";
        String action = "BACKUP";
        if (lexer.identifierEquals(FnvHash.Constants.DATA)) {
            lexer.nextToken();
            accept(Token.INTO);
            type = "BACKUP_DATA";
            for (; ; ) {
                stmt.getProperties().add(new SQLCharExpr(lexer.stringVal()));
                accept(Token.LITERAL_CHARS);
                if (lexer.token() == Token.COMMA) {
                    lexer.nextToken();
                    continue;
                }
                break;
            }
        } else if (lexer.identifierEquals(FnvHash.Constants.LOG)) {
            type = "BACKUP_LOG";
            lexer.nextToken();
            if (lexer.identifierEquals("LIST_LOGS")) {
                lexer.nextToken();
                action = "LIST_LOG";
            } else if (lexer.identifierEquals(FnvHash.Constants.STATUS)) {
                lexer.nextToken();
                action = "STATUS";
            } else if (lexer.token() == Token.INTO) {
                lexer.nextToken();
                for (; ; ) {
                    stmt.getProperties().add(new SQLCharExpr(lexer.stringVal()));
                    accept(Token.LITERAL_CHARS);
                    if (lexer.token() == Token.COMMA) {
                        lexer.nextToken();
                        continue;
                    }
                    break;
                }
            }
        } else if (lexer.identifierEquals("CANCEL")) {
            lexer.nextToken();
            type = "BACKUP_DATA";
            action = "BACKUP_CANCEL";
            stmt.getProperties().add(new SQLCharExpr(lexer.stringVal()));
            accept(Token.LITERAL_CHARS);
        }
        stmt.setType(new SQLIdentifierExpr(type));
        stmt.setAction(new SQLIdentifierExpr(action));
        return stmt;
    }

    private SQLStatement parseRestore() {
        lexer.nextToken();
        String type = "DATA";
        SQLRestoreStatement stmt = new SQLRestoreStatement();
        if (lexer.identifierEquals(FnvHash.Constants.DATA)) {
            lexer.nextToken();
            type = "DATA";
        } else if (lexer.identifierEquals(FnvHash.Constants.LOG)) {
            lexer.nextToken();
            type = "LOG";
        }
        stmt.setType(new SQLIdentifierExpr(type));
        accept(Token.FROM);
        for (; ; ) {
            stmt.getProperties().add(new SQLCharExpr(lexer.stringVal()));
            accept(Token.LITERAL_CHARS);
            if (lexer.token() == Token.COMMA) {
                lexer.nextToken();
                continue;
            }
            break;
        }
        return stmt;
    }

    private SQLStatement parseBuildTable() {
        lexer.nextToken();
        SQLBuildTableStatement stmt = new SQLBuildTableStatement();
        accept(Token.TABLE);
        stmt.setTable(this.exprParser.name());
        if (lexer.identifierEquals(FnvHash.Constants.VERSION)) {
            lexer.nextToken();
            accept(Token.EQ);
            stmt.setVersion(this.exprParser.integerExpr());
        }
        if (lexer.token() == Token.WITH) {
            lexer.nextToken();
            acceptIdentifier("SPLIT");
            stmt.setWithSplit(true);
        }
        if (lexer.identifierEquals(FnvHash.Constants.FORCE)) {
            lexer.nextToken();
            accept(Token.EQ);
            if (lexer.token() == Token.TRUE) {
                lexer.nextToken();
                stmt.setForce(true);
            } else if (lexer.token() == Token.FALSE) {
                lexer.nextToken();
                stmt.setForce(false);
            }
        }
        return stmt;
    }

    private SQLStatement parseCancelJob() {
        lexer.nextToken();
        // DRDS async DDL.
        if (isEnabled(SQLParserFeature.DRDSAsyncDDL) && lexer.identifierEquals("DDL")) {
            // CANCEL DDL <job_id> [, <job_id>] ...
            lexer.nextToken();
            DrdsCancelDDLJob cancelDDLJob = new DrdsCancelDDLJob();
            while (true) {
                cancelDDLJob.addJobId(lexer.integerValue().longValue());
                accept(Token.LITERAL_INT);
                if (Token.COMMA == lexer.token()) {
                    lexer.nextToken();
                } else if (lexer.token() == Token.EOF || lexer.token() == Token.SEMI) {
                    break;
                } else {
                    throw new ParserException("syntax error, expect job id, actual " + lexer.token() + ", " + lexer.info());
                }
            }
            return cancelDDLJob;
        }
        SQLCancelJobStatement stmt = new SQLCancelJobStatement();
        if (lexer.identifierEquals("JOB")) {
            lexer.nextToken();
        } else if (lexer.identifierEquals("LOAD_JOB")) {
            lexer.nextToken();
        } else if (lexer.identifierEquals("SYNC_JOB")) {
            lexer.nextToken();
            stmt.setImport(true);
        }
        stmt.setJobName(this.exprParser.name());
        return stmt;
    }

    protected SQLStatement parseExportTable() {
        accept(Token.TABLE);
        SQLExportTableStatement stmt = new SQLExportTableStatement();
        stmt.setTable(new SQLExprTableSource(this.exprParser.name()));
        return stmt;
    }

    protected SQLStatement parseExportDB() {
        accept(Token.DATABASE);
        SQLExportDatabaseStatement stmt = new SQLExportDatabaseStatement();
        stmt.setDb(this.exprParser.name());

        if (lexer.identifierEquals("REALTIME")) {
            lexer.nextToken();
            accept(Token.EQ);
            if ("y".equalsIgnoreCase(lexer.stringVal())) {
                lexer.nextToken();
                stmt.setRealtime(true);
            } else if ("n".equalsIgnoreCase(lexer.stringVal())) {
                lexer.nextToken();
                stmt.setRealtime(false);
            } else {
                throw new ParserException("Invalid 'realtime' option, should be 'Y' or 'N'. ");
            }
        }
        return stmt;
    }

    protected SQLStatement parseRaftLeaderTransfer() {
        acceptIdentifier("RAFT_LEADER_TRANSFER");
        MySqlRaftLeaderTransferStatement stmt = new MySqlRaftLeaderTransferStatement();
        acceptIdentifier("SHARD");
        accept(Token.EQ);
        stmt.setShard(exprParser.charExpr());
        accept(Token.FROM);
        accept(Token.EQ);
        stmt.setFrom(exprParser.charExpr());
        accept(Token.TO);
        accept(Token.EQ);
        stmt.setTo(exprParser.charExpr());
        acceptIdentifier("TIMEOUT");
        accept(Token.EQ);
        stmt.setTimeout(exprParser.integerExpr());
        return stmt;
    }

    protected SQLStatement parseRaftMemeberChange() {
        acceptIdentifier("RAFT_MEMBER_CHANGE");
        MySqlRaftMemberChangeStatement stmt = new MySqlRaftMemberChangeStatement();
        if (lexer.identifierEquals("NOLEADER")) {
            lexer.nextToken();
            stmt.setNoLeader(true);
        }
        acceptIdentifier("SHARD");
        accept(Token.EQ);
        stmt.setShard(exprParser.charExpr());
        acceptIdentifier("HOST");
        accept(Token.EQ);
        stmt.setHost(exprParser.charExpr());
        acceptIdentifier("STATUS");
        accept(Token.EQ);
        stmt.setStatus(exprParser.charExpr());
        if (lexer.identifierEquals(FnvHash.Constants.FORCE)) {
            lexer.nextToken();
            stmt.setForce(true);
        }
        return stmt;
    }

    protected SQLStatement parseMigrate() {
        MySqlMigrateStatement stmt = new MySqlMigrateStatement();
        acceptIdentifier("MIGRATE");
        accept(Token.DATABASE);
        stmt.setSchema(exprParser.name());
        acceptIdentifier("SHARDS");
        accept(Token.EQ);
        stmt.setShardNames(exprParser.charExpr());
        if (lexer.token() == Token.GROUP) {
            lexer.nextToken();
            stmt.setMigrateType(new SQLIntegerExpr(0));
        } else if (lexer.identifierEquals(FnvHash.Constants.HOST)) {
            lexer.nextToken();
            stmt.setMigrateType(new SQLIntegerExpr(1));
        }
        accept(Token.FROM);
        stmt.setFromInsId(exprParser.charExpr());
        if (lexer.token() == Token.VARIANT) {
            lexer.nextToken();
            stmt.setFromInsIp(exprParser.charExpr());
            String variant = lexer.stringVal();
            Integer number = Integer.valueOf(variant.substring(1, variant.length()));
            stmt.setFromInsPort(new SQLIntegerExpr(number));
            accept(Token.VARIANT);
            accept(Token.VARIANT);
            stmt.setFromInsStatus(exprParser.charExpr());
        }
        accept(Token.TO);
        stmt.setToInsId(exprParser.charExpr());
        if (lexer.token() == Token.VARIANT) {
            lexer.nextToken();
            stmt.setToInsIp(exprParser.charExpr());
            String variant = lexer.stringVal();
            Integer number = Integer.valueOf(variant.substring(1, variant.length()));
            stmt.setToInsPort(new SQLIntegerExpr(number));
            accept(Token.VARIANT);
            accept(Token.VARIANT);
            stmt.setToInsStatus(exprParser.charExpr());
        }
        return stmt;
    }

    protected SQLStatement parseImportDB() {
        accept(Token.DATABASE);
        SQLImportDatabaseStatement stmt = new SQLImportDatabaseStatement();
        stmt.setDb(this.exprParser.name());
        if (lexer.identifierEquals(FnvHash.Constants.STATUS)) {
            lexer.nextToken();
            accept(Token.EQ);
            stmt.setStatus(this.exprParser.name());
        }
        return stmt;
    }

    protected SQLStatement parseImportTable() {
        SQLImportTableStatement stmt = new SQLImportTableStatement();
        if (lexer.identifierEquals(FnvHash.Constants.EXTERNAL)) {
            lexer.nextToken();
            stmt.setExtenal(true);
        }
        accept(Token.TABLE);
        stmt.setTable(new SQLExprTableSource(this.exprParser.name()));
        acceptIdentifier("VERSION");
        accept(Token.EQ);
        stmt.setVersion(this.exprParser.integerExpr());

        if (lexer.identifierEquals("BUILD")) {
            lexer.nextToken();
            accept(Token.EQ);
            if ("Y".equalsIgnoreCase(lexer.stringVal())) {
                lexer.nextToken();
                stmt.setUsingBuild(true);
            } else if ("N".equalsIgnoreCase(lexer.stringVal())) {
                lexer.nextToken();
                stmt.setUsingBuild(false);
            } else {
                throw new ParserException("Invalid 'build' option, should be 'Y' or 'N'. ");
            }
        }
        return stmt;
    }

    protected SQLStatement parseSubmitJob() {
        SQLSubmitJobStatement stmt = new SQLSubmitJobStatement();
        if (lexer.identifierEquals("AWAIT")) {
            lexer.nextToken();
            stmt.setAwait(true);
        }
        stmt.setStatment(this.parseStatement());
        return stmt;
    }

    public SQLStatement parseSync() {
        lexer.nextToken();
        if (lexer.identifierEquals("RAFT_LEADER_TRANSFER")) {
            return parseRaftLeaderTransfer();
        } else if (lexer.identifierEquals("RAFT_MEMBER_CHANGE")) {
            return parseRaftMemeberChange();
        } else {
            acceptIdentifier("META");
            acceptIdentifier("TABLES");
            SQLSyncMetaStatement stmt = new SQLSyncMetaStatement();
            if (lexer.token() == Token.FROM) {
                lexer.nextToken();
                stmt.setFrom(this.exprParser.name());
            }
            if (lexer.token() == Token.LIKE) {
                lexer.nextToken();
                stmt.setLike(this.exprParser.expr());
            }
            return stmt;
        }
    }

    public SQLStatement parseFlush() {
        acceptIdentifier("FLUSH");
        MySqlFlushStatement stmt = new MySqlFlushStatement();

        if (lexer.identifierEquals("NO_WRITE_TO_BINLOG")) {
            lexer.nextToken();
            stmt.setNoWriteToBinlog(true);
        }

        if (lexer.identifierEquals("LOCAL")) {
            lexer.nextToken();
            stmt.setLocal(true);
        }

        for (; ; ) {
            if (lexer.token() == Token.BINARY || lexer.identifierEquals("BINARY")) {
                lexer.nextToken();
                acceptIdentifier("LOGS");
                stmt.setBinaryLogs(true);
            } else if (lexer.identifierEquals("DES_KEY_FILE")) {
                lexer.nextToken();
                stmt.setDesKeyFile(true);
            } else if (lexer.identifierEquals("ENGINE")) {
                lexer.nextToken();
                acceptIdentifier("LOGS");
                stmt.setEngineLogs(true);
            } else if (lexer.identifierEquals("ERROR")) {
                lexer.nextToken();
                acceptIdentifier("LOGS");
                stmt.setErrorLogs(true);
            } else if (lexer.identifierEquals("GENERAL")) {
                lexer.nextToken();
                acceptIdentifier("LOGS");
                stmt.setGeneralLogs(true);
            } else if (lexer.identifierEquals("HOSTS")) {
                lexer.nextToken();
                stmt.setHots(true);
            } else if (lexer.identifierEquals("LOGS")) {
                lexer.nextToken();
                stmt.setLogs(true);
            } else if (lexer.identifierEquals("PRIVILEGES")) {
                lexer.nextToken();
                stmt.setPrivileges(true);
            } else if (lexer.identifierEquals("OPTIMIZER_COSTS")) {
                lexer.nextToken();
                stmt.setOptimizerCosts(true);
            } else if (lexer.identifierEquals("QUERY")) {
                lexer.nextToken();
                accept(Token.CACHE);
                stmt.setQueryCache(true);
            } else if (lexer.identifierEquals("RELAY")) {
                lexer.nextToken();
                acceptIdentifier("LOGS");
                stmt.setRelayLogs(true);
                if (lexer.token() == Token.FOR) {
                    lexer.nextToken();
                    acceptIdentifier("CHANNEL");
                    stmt.setRelayLogsForChannel(this.exprParser.primary());
                }
            } else if (lexer.identifierEquals("SLOW")) {
                lexer.nextToken();
                acceptIdentifier("LOGS");
                stmt.setSlowLogs(true);
            } else if (lexer.identifierEquals(STATUS)) {
                lexer.nextToken();
                stmt.setStatus(true);
            } else if (lexer.identifierEquals("USER_RESOURCES")) {
                lexer.nextToken();
                stmt.setUserResources(true);
            } else if (lexer.token() == Token.COMMA) {
                lexer.nextToken();
                continue;
            } else {
                break;
            }
        }

        if (lexer.identifierEquals("TABLES") || lexer.token() == Token.TABLE) {
            lexer.nextToken();
            stmt.setTableOption(true);
            if (lexer.token() == Token.WITH) {
                lexer.nextToken();
                acceptIdentifier("READ");
                accept(Token.LOCK);
                stmt.setWithReadLock(true);
            }
            for (; ; ) {
                if (lexer.token() == Token.IDENTIFIER) {
                    for (; ; ) {
                        SQLName name = this.exprParser.name();
                        stmt.addTable(name);

                        if (lexer.token() == Token.COMMA) {
                            lexer.nextToken();
                            continue;
                        }
                        break;
                    }
                    break;
                }
                break;
            }

            if (stmt.getTables().size() != 0) {
                if (lexer.token() == Token.FOR) {
                    lexer.nextToken();
                    acceptIdentifier("EXPORT");
                    stmt.setForExport(true);
                } else if (lexer.token() == Token.WITH) {
                    lexer.nextToken();
                    acceptIdentifier("READ");
                    accept(Token.LOCK);
                    stmt.setWithReadLock(true);
                } else if (lexer.identifierEquals(FnvHash.Constants.VERSION)) {
                    lexer.nextToken();
                    accept(Token.EQ);
                    stmt.setVersion(this.exprParser.integerExpr());
                }
            }
        }
        return stmt;
    }

    public SQLBlockStatement parseBlock() {
        SQLBlockStatement block = new SQLBlockStatement();
        block.setDbType(dbType);
        accept(Token.BEGIN);
        List<SQLStatement> statementList = block.getStatementList();
        this.parseStatementList(statementList, -1, block);
        if (lexer.token() != Token.END
                && statementList.size() > 0
                && (statementList.get(statementList.size() - 1) instanceof SQLCommitStatement
                || statementList.get(statementList.size() - 1) instanceof SQLRollbackStatement)) {
            block.setEndOfCommit(true);
            return block;
        }
        accept(Token.END);
        return block;
    }

    public MySqlExplainStatement parseDescribe() {
        // see https://dev.mysql.com/doc/refman/5.7/en/explain.html
        MySqlExplainStatement describe = new MySqlExplainStatement();
        // {DESCRIBE | DESC}
        if (lexer.token() == Token.DESC || lexer.identifierEquals(DESCRIBE)) {
            lexer.nextToken();
            describe.setDescribe(true);
        } else {
            throw new ParserException("expect one of {DESCRIBE | DESC} , actual " + lexer.token() + ", " + lexer.info());
        }
        return parseExplain(describe);
    }

    public MySqlExplainStatement parseExplain() {
        // see https://dev.mysql.com/doc/refman/5.7/en/explain.html
        MySqlExplainStatement explain = new MySqlExplainStatement();
        explain.setSourceLine(lexer.getPosLine());
        explain.setSourceLine(lexer.getPosColumn());
        // {EXPLAIN}
        if (lexer.token() == Token.EXPLAIN) {
            lexer.nextToken();
        } else {
            throw new ParserException("expect EXPLAIN , actual " + lexer.token() + ", " + lexer.info());
        }
        return parseExplain(explain);
    }

    private MySqlExplainStatement parseExplain(MySqlExplainStatement explain) {
        if (lexer.identifierEquals(FnvHash.Constants.PLAN)) {
            Lexer.SavePoint mark = lexer.mark();
            lexer.nextToken();
            if (lexer.token() == Token.FOR) {
                lexer.nextToken();
            } else {
                lexer.reset(mark);
            }
        }
        if (lexer.token() == Token.ANALYZE) {
            lexer.nextToken();
            explain.setType("ANALYZE");
        }
        if (lexer.token() == Token.HINT) {
            List<SQLCommentHint> hints = this.exprParser.parseHints();
            explain.setHints(hints);
        }
        // see https://dev.mysql.com/doc/refman/5.7/en/explain.html
        switch (dbType) {
            case mysql:
            case ads:
            case presto:
            case trino:
                Lexer.SavePoint mark = lexer.mark();

                if (lexer.token() == Token.LPAREN) {
                    lexer.nextToken();
                    if (lexer.token() == Token.SELECT) {
                        lexer.reset(mark);
                        break;
                    }
                    for (; ; ) {
                        if (lexer.identifierEquals("FORMAT")) {
                            lexer.nextToken();
                            String format = lexer.stringVal();
                            explain.setFormat(format);
                            lexer.nextToken();
                        } else if (lexer.identifierEquals("TYPE")) {
                            lexer.nextToken();
                            String type = lexer.stringVal();
                            explain.setType(type);
                            lexer.nextToken();
                        } else {
                            break;
                        }

                        if (lexer.token() == Token.COMMA) {
                            lexer.nextToken();
                            continue;
                        } else {
                            break;
                        }
                    }
                    accept(Token.RPAREN);
                    explain.setParenthesis(true);
                }
                break;
            default:
                break;
        }

        boolean table = false;
        if (lexer.token() == Token.IDENTIFIER) {
            final long hash = lexer.hash_lower();
            String stringVal = lexer.stringVal();
            if (hash == FnvHash.Constants.EXTENDED) {
                explain.setExtended(true);
                lexer.nextToken();
            } else if (hash == FnvHash.Constants.PARTITIONS) {
                explain.setType(stringVal);
                lexer.nextToken();
            } else if (hash == FnvHash.Constants.OPTIMIZER) {
                explain.setOptimizer(true);
                lexer.nextToken();
            } else if (hash == FnvHash.Constants.FORMAT) {
                lexer.nextToken();
                accept(Token.EQ);
                String format = lexer.stringVal();
                explain.setFormat(format);
                accept(Token.IDENTIFIER);
            } else {
                explain.setTableName(exprParser.name());
                if (lexer.token() == Token.IDENTIFIER) {
                    explain.setColumnName(exprParser.name());
                } else if (lexer.token() == Token.LITERAL_CHARS) {
                    explain.setWild(exprParser.expr());
                }
                table = true;
            }
        }
        if (lexer.token() == Token.DISTRIBUTE) {
            lexer.nextToken();
            acceptIdentifier("INFO");
            explain.setDistributeInfo(true);
        }
        if (lexer.token() == Token.FOR) {
            lexer.nextToken();
            acceptIdentifier(CONNECTION);
            explain.setConnectionId(exprParser.expr());
        } else if (!table) {
            explain.setStatement(this.parseStatement());
        }
        return explain;
    }

    public SQLStatement parseShow() {
        accept(Token.SHOW);
        if (lexer.token() == Token.COMMENT) {
            lexer.nextToken();
        }
        boolean isPhysical = false;
        if (lexer.identifierEquals(FnvHash.Constants.PHYSICAL)) {
            lexer.nextToken();
            isPhysical = true;
        }
        boolean full = false;
        if (lexer.token() == Token.FULL) {
            lexer.nextToken();
            full = true;
        } else if (lexer.token() == Token.HINT) {
            String hints = lexer.stringVal().toLowerCase();
            if (hints.endsWith(" full")
                    && hints.length() <= 11
                    && hints.charAt(0) == '!'
                    && hints.charAt(1) == '5') {
                lexer.nextToken();
                full = true;
            }
        }
        if (lexer.identifierEquals("COBAR_STATUS")) {
            lexer.nextToken();
            return new CobarShowStatus();
        }
        // MySqlShowSlaveHostsStatement
        throw new ParserException("TODO " + lexer.info());
    }

    public SQLStartTransactionStatement parseStart() {
        acceptIdentifier("START");
        acceptIdentifier("TRANSACTION");
        SQLStartTransactionStatement stmt = new SQLStartTransactionStatement(dbType);
        if (lexer.token() == Token.WITH) {
            lexer.nextToken();
            acceptIdentifier("CONSISTENT");
            acceptIdentifier("SNAPSHOT");
            stmt.setConsistentSnapshot(true);
        }
        if (lexer.token() == Token.BEGIN) {
            lexer.nextToken();
            stmt.setBegin(true);
            if (lexer.identifierEquals("WORK")) {
                lexer.nextToken();
                stmt.setWork(true);
            }
        }
        if (lexer.token() == Token.HINT) {
            stmt.setHints(this.exprParser.parseHints());
        }
        if (lexer.identifierEquals(FnvHash.Constants.ISOLATION)) {
            lexer.nextToken();
            acceptIdentifier("LEVEL");

            if (lexer.identifierEquals(FnvHash.Constants.READ)) {
                lexer.nextToken();
                if (lexer.identifierEquals(FnvHash.Constants.UNCOMMITTED)) {
                    lexer.nextToken();
                    stmt.setIsolationLevel(SQLStartTransactionStatement.IsolationLevel.READ_UNCOMMITTED);
                } else if (lexer.identifierEquals(FnvHash.Constants.COMMITTED)) {
                    lexer.nextToken();
                    stmt.setIsolationLevel(SQLStartTransactionStatement.IsolationLevel.READ_COMMITTED);
                } else {
                    throw new ParserException(lexer.info());
                }
            } else if (lexer.identifierEquals(FnvHash.Constants.REPEATABLE)) {
                lexer.nextToken();
                acceptIdentifier("READ");
                stmt.setIsolationLevel(SQLStartTransactionStatement.IsolationLevel.REPEATABLE_READ);
            } else if (lexer.identifierEquals(FnvHash.Constants.SERIALIZABLE)) {
                lexer.nextToken();
                stmt.setIsolationLevel(SQLStartTransactionStatement.IsolationLevel.SERIALIZABLE);
            } else {
                throw new ParserException(lexer.info());
            }
        }
        if (lexer.identifierEquals(FnvHash.Constants.READ)) {
            lexer.nextToken();
            acceptIdentifier("ONLY");
            stmt.setReadOnly(true);
        }
        return stmt;
    }

    @Override
    public SQLStatement parseRollback() {
        acceptIdentifier("ROLLBACK");
        // DRDS async DDL.
        if (isEnabled(SQLParserFeature.DRDSAsyncDDL) && lexer.identifierEquals("DDL")) {
            // ROLLBACK DDL <job_id> [, <job_id>] ...
            lexer.nextToken();
            DrdsRollbackDDLJob stmt = new DrdsRollbackDDLJob();
            while (true) {
                stmt.addJobId(lexer.integerValue().longValue());
                accept(Token.LITERAL_INT);
                if (Token.COMMA == lexer.token()) {
                    lexer.nextToken();
                } else if (lexer.token() == Token.EOF || lexer.token() == Token.SEMI) {
                    break;
                } else {
                    throw new ParserException("syntax error, expect job id, actual " + lexer.token() + ", " + lexer.info());
                }
            }
            return stmt;
        }
        SQLRollbackStatement stmt = new SQLRollbackStatement();
        if (lexer.identifierEquals("WORK")) {
            lexer.nextToken();
        }
        if (lexer.token() == Token.AND) {
            lexer.nextToken();
            if (lexer.token() == Token.NOT) {
                lexer.nextToken();
                acceptIdentifier(CHAIN);
                stmt.setChain(Boolean.FALSE);
            } else {
                acceptIdentifier(CHAIN);
                stmt.setChain(Boolean.TRUE);
            }
        }
        if (lexer.token() == Token.TO) {
            lexer.nextToken();

            if (lexer.identifierEquals("SAVEPOINT")) {
                lexer.nextToken();
            }
            stmt.setTo(this.exprParser.name());
        }
        return stmt;
    }

    public SQLStatement parseCommit() {
        acceptIdentifier("COMMIT");
        SQLCommitStatement stmt = new SQLCommitStatement();
        if (lexer.identifierEquals("WORK")) {
            lexer.nextToken();
            stmt.setWork(true);
        }
        if (lexer.token() == Token.AND) {
            lexer.nextToken();
            if (lexer.token() == Token.NOT) {
                lexer.nextToken();
                acceptIdentifier(CHAIN);
                stmt.setChain(Boolean.FALSE);
            } else {
                acceptIdentifier(CHAIN);
                stmt.setChain(Boolean.TRUE);
            }
        }
        return stmt;
    }

    public SQLReplaceStatement parseReplace() {
        SQLReplaceStatement stmt = new SQLReplaceStatement();
        stmt.setDbType(DbType.mysql);
        List<SQLCommentHint> list = new ArrayList<SQLCommentHint>();
        while (lexer.token() == Token.HINT) {
            this.exprParser.parseHints(list);
        }
        stmt.setHeadHints(list);
        accept(Token.REPLACE);
        while (lexer.token() == Token.HINT) {
            this.exprParser.parseHints(stmt.getHints());
        }
        if (lexer.token() == Token.COMMENT) {
            lexer.nextToken();
        }
        if (lexer.identifierEquals(FnvHash.Constants.LOW_PRIORITY)) {
            stmt.setLowPriority(true);
            lexer.nextToken();
        }
        if (lexer.identifierEquals(FnvHash.Constants.DELAYED)) {
            stmt.setDelayed(true);
            lexer.nextToken();
        }
        if (lexer.token() == Token.INTO) {
            lexer.nextToken();
        }
        SQLName tableName = exprParser.name();
        stmt.setTableName(tableName);
        if (lexer.token() == Token.PARTITION) {
            lexer.nextToken();
            accept(Token.LPAREN);
            for (; ; ) {
                SQLAssignItem ptExpr = new SQLAssignItem();
                ptExpr.setTarget(this.exprParser.name());
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                    SQLExpr ptValue = this.exprParser.expr();
                    ptExpr.setValue(ptValue);
                }
                stmt.addPartition(ptExpr);
                if (lexer.token() != Token.COMMA) {
                    break;
                } else {
                    lexer.nextToken();
                }
            }
            accept(Token.RPAREN);
        }
        if (lexer.token() == Token.LPAREN) {
            lexer.nextToken();
            if (lexer.token() == Token.SELECT) {
                SQLQueryExpr queryExpr = (SQLQueryExpr) this.exprParser.expr();
                stmt.setQuery(queryExpr);
            } else {
                this.exprParser.exprList(stmt.getColumns(), stmt);
            }
            accept(Token.RPAREN);
        }
        if (lexer.token() == Token.VALUES || lexer.identifierEquals("VALUE")) {
            lexer.nextToken();
            parseValueClause(stmt.getValuesList(), null, 0, stmt);
        } else if (lexer.token() == Token.SELECT) {
            SQLQueryExpr queryExpr = (SQLQueryExpr) this.exprParser.expr();
            stmt.setQuery(queryExpr);
        } else if (lexer.token() == Token.SET) {
            lexer.nextToken();
            SQLInsertStatement.ValuesClause values = new SQLInsertStatement.ValuesClause();
            values.setParent(stmt);
            stmt.getValuesList().add(values);
            for (; ; ) {
                stmt.addColumn(this.exprParser.name());
                if (lexer.token() == Token.COLONEQ) {
                    lexer.nextToken();
                } else {
                    accept(Token.EQ);
                }
                values.addValue(this.exprParser.expr());
                if (lexer.token() == (Token.COMMA)) {
                    lexer.nextToken();
                    continue;
                }
                break;
            }
        } else if (lexer.token() == Token.LPAREN) {
            SQLSelect select = this.createSQLSelectParser().select();
            SQLQueryExpr queryExpr = new SQLQueryExpr(select);
            stmt.setQuery(queryExpr);
        }
        return stmt;
    }

    protected SQLStatement parseLoad() {
        acceptIdentifier("LOAD");
        if (lexer.identifierEquals("DATA")) {
            SQLStatement stmt = parseLoadDataInFile();
            return stmt;
        }
        if (lexer.identifierEquals("XML")) {
            SQLStatement stmt = parseLoadXml();
            return stmt;
        }
        throw new ParserException("TODO. " + lexer.info());
    }

    protected MySqlLoadXmlStatement parseLoadXml() {
        acceptIdentifier("XML");
        MySqlLoadXmlStatement stmt = new MySqlLoadXmlStatement();
        if (lexer.identifierEquals(FnvHash.Constants.LOW_PRIORITY)) {
            stmt.setLowPriority(true);
            lexer.nextToken();
        }
        if (lexer.identifierEquals("CONCURRENT")) {
            stmt.setConcurrent(true);
            lexer.nextToken();
        }
        if (lexer.identifierEquals(LOCAL)) {
            stmt.setLocal(true);
            lexer.nextToken();
        }
        acceptIdentifier("INFILE");
        SQLLiteralExpr fileName = (SQLLiteralExpr) exprParser.expr();
        stmt.setFileName(fileName);
        if (lexer.token() == Token.REPLACE) {
            stmt.setReplicate(true);
            lexer.nextToken();
        }
        if (lexer.identifierEquals(FnvHash.Constants.IGNORE)) {
            stmt.setIgnore(true);
            lexer.nextToken();
        }
        accept(Token.INTO);
        accept(Token.TABLE);
        SQLName tableName = exprParser.name();
        stmt.setTableName(tableName);
        if (lexer.identifierEquals(FnvHash.Constants.CHARACTER)) {
            lexer.nextToken();
            accept(Token.SET);
            if (lexer.token() != Token.LITERAL_CHARS) {
                throw new ParserException("syntax error, illegal charset. " + lexer.info());
            }
            String charset = lexer.stringVal();
            lexer.nextToken();
            stmt.setCharset(charset);
        }
        if (lexer.identifierEquals("ROWS")) {
            lexer.nextToken();
            acceptIdentifier("IDENTIFIED");
            accept(Token.BY);
            SQLExpr rowsIdentifiedBy = exprParser.expr();
            stmt.setRowsIdentifiedBy(rowsIdentifiedBy);
        }
        if (lexer.identifierEquals(FnvHash.Constants.IGNORE)) {
            throw new ParserException("TODO. " + lexer.info());
        }
        if (lexer.token() == Token.SET) {
            throw new ParserException("TODO. " + lexer.info());
        }
        return stmt;
    }

    protected MySqlLoadDataInFileStatement parseLoadDataInFile() {
        acceptIdentifier("DATA");
        MySqlLoadDataInFileStatement stmt = new MySqlLoadDataInFileStatement();
        if (lexer.identifierEquals(FnvHash.Constants.LOW_PRIORITY)) {
            stmt.setLowPriority(true);
            lexer.nextToken();
        }
        if (lexer.identifierEquals("CONCURRENT")) {
            stmt.setConcurrent(true);
            lexer.nextToken();
        }
        if (lexer.identifierEquals(LOCAL)) {
            stmt.setLocal(true);
            lexer.nextToken();
        }
        acceptIdentifier("INFILE");
        SQLLiteralExpr fileName = (SQLLiteralExpr) exprParser.expr();
        stmt.setFileName(fileName);
        if (lexer.token() == Token.REPLACE) {
            stmt.setReplicate(true);
            lexer.nextToken();
        }
        if (lexer.identifierEquals(FnvHash.Constants.IGNORE)) {
            stmt.setIgnore(true);
            lexer.nextToken();
        }
        accept(Token.INTO);
        accept(Token.TABLE);
        SQLName tableName = exprParser.name();
        stmt.setTableName(tableName);
        if (lexer.identifierEquals(FnvHash.Constants.CHARACTER)) {
            lexer.nextToken();
            accept(Token.SET);
            if (lexer.token() != Token.LITERAL_CHARS) {
                throw new ParserException("syntax error, illegal charset. " + lexer.info());
            }
            String charset = lexer.stringVal();
            lexer.nextToken();
            stmt.setCharset(charset);
        }
        if (lexer.identifierEquals("FIELDS") || lexer.identifierEquals("COLUMNS")) {
            lexer.nextToken();
            if (lexer.identifierEquals("TERMINATED")) {
                lexer.nextToken();
                accept(Token.BY);
                if (lexer.token() == Token.LITERAL_CHARS) {
                    stmt.setColumnsTerminatedBy(new SQLCharExpr(lexer.stringVal()));
                    lexer.nextToken();
                } else {
                    SQLExpr primary = this.exprParser.primary();
                    if (primary instanceof SQLHexExpr) {
                        stmt.setColumnsTerminatedBy((SQLHexExpr) primary);
                    } else {
                        throw new ParserException("invalid expr for columns terminated : " + primary);
                    }
                }
            }
            if (lexer.identifierEquals("OPTIONALLY")) {
                stmt.setColumnsEnclosedOptionally(true);
                lexer.nextToken();
            }
            if (lexer.identifierEquals("ENCLOSED")) {
                lexer.nextToken();
                accept(Token.BY);
                stmt.setColumnsEnclosedBy(new SQLCharExpr(lexer.stringVal()));
                lexer.nextToken();
            }
            if (lexer.identifierEquals("ESCAPED")) {
                lexer.nextToken();
                accept(Token.BY);
                stmt.setColumnsEscaped(new SQLCharExpr(lexer.stringVal()));
                lexer.nextToken();
            }
        }
        if (lexer.identifierEquals("LINES")) {
            lexer.nextToken();
            if (lexer.identifierEquals("STARTING")) {
                lexer.nextToken();
                accept(Token.BY);
                if (lexer.token() == Token.LITERAL_CHARS) {
                    stmt.setLinesStartingBy(new SQLCharExpr(lexer.stringVal()));
                    lexer.nextToken();
                } else {
                    SQLExpr primary = this.exprParser.primary();
                    if (primary instanceof SQLHexExpr) {
                        stmt.setLinesStartingBy((SQLHexExpr) primary);
                    } else {
                        throw new ParserException("invalid expr for lines starting : " + primary);
                    }
                }
            }
            if (lexer.identifierEquals("TERMINATED")) {
                lexer.nextToken();
                accept(Token.BY);
                if (lexer.token() == Token.LITERAL_CHARS) {
                    stmt.setLinesTerminatedBy(new SQLCharExpr(lexer.stringVal()));
                    lexer.nextToken();
                } else {
                    SQLExpr primary = this.exprParser.primary();
                    if (primary instanceof SQLHexExpr) {
                        stmt.setLinesTerminatedBy((SQLHexExpr) primary);
                    } else {
                        throw new ParserException("invalid expr for lines terminated : " + primary);
                    }
                }
            }
        }
        if (lexer.identifierEquals(FnvHash.Constants.IGNORE)) {
            lexer.nextToken();
            stmt.setIgnoreLinesNumber(this.exprParser.expr());
            acceptIdentifier("LINES");
        }
        if (lexer.token() == Token.LPAREN) {
            lexer.nextToken();
            this.exprParser.exprList(stmt.getColumns(), stmt);
            accept(Token.RPAREN);
        }
        if (lexer.token() == Token.SET) {
            lexer.nextToken();
            this.exprParser.exprList(stmt.getSetList(), stmt);
        }
        return stmt;
    }

    public MySqlPrepareStatement parsePrepare() {
        acceptIdentifier("PREPARE");
        SQLName name = exprParser.name();
        accept(Token.FROM);
        SQLExpr from = exprParser.expr();
        return new MySqlPrepareStatement(name, from);
    }

    public MySqlExecuteStatement parseExecute() {
        MySqlExecuteStatement stmt = new MySqlExecuteStatement();
        SQLName statementName = exprParser.name();
        stmt.setStatementName(statementName);
        if (lexer.identifierEquals("USING")) {
            lexer.nextToken();
            exprParser.exprList(stmt.getParameters(), stmt);
        } else if (lexer.token() == Token.IDENTIFIER) {
            exprParser.exprList(stmt.getParameters(), stmt);
        }
        return stmt;
    }

    public MySqlExecuteForAdsStatement parseExecuteForAds() {
        MySqlExecuteForAdsStatement stmt = new MySqlExecuteForAdsStatement();
        stmt.setAction(exprParser.name());
        stmt.setRole(exprParser.name());
        stmt.setTargetId(exprParser.charExpr());
        if (lexer.token() == Token.IDENTIFIER) {
            stmt.setStatus(exprParser.name());
        }
        return stmt;
    }

    public MysqlDeallocatePrepareStatement parseDeallocatePrepare() {
        acceptIdentifier("DEALLOCATE");
        acceptIdentifier("PREPARE");
        MysqlDeallocatePrepareStatement stmt = new MysqlDeallocatePrepareStatement();
        SQLName statementName = exprParser.name();
        stmt.setStatementName(statementName);
        return stmt;
    }

    public SQLInsertStatement parseInsert() {
        MySqlInsertStatement stmt = new MySqlInsertStatement();
        SQLName tableName = null;
        if (lexer.token() == Token.INSERT) {
            lexer.nextToken();
            for (; ; ) {
                if (lexer.token() == Token.IDENTIFIER) {
                    long hash = lexer.hash_lower();

                    if (hash == FnvHash.Constants.LOW_PRIORITY) {
                        stmt.setLowPriority(true);
                        lexer.nextToken();
                        continue;
                    }
                    if (hash == FnvHash.Constants.DELAYED) {
                        stmt.setDelayed(true);
                        lexer.nextToken();
                        continue;
                    }
                    if (hash == FnvHash.Constants.HIGH_PRIORITY) {
                        stmt.setHighPriority(true);
                        lexer.nextToken();
                        continue;
                    }
                    if (hash == FnvHash.Constants.IGNORE) {
                        stmt.setIgnore(true);
                        lexer.nextToken();
                        continue;
                    }
                    if (hash == FnvHash.Constants.ROLLBACK_ON_FAIL) {
                        stmt.setRollbackOnFail(true);
                        lexer.nextToken();
                        continue;
                    }
                }
                break;
            }
            if (lexer.token() == Token.HINT) {
                List<SQLCommentHint> hints = this.exprParser.parseHints();
                stmt.setHints(hints);
            }
            if (lexer.token() == Token.INTO) {
                lexer.nextToken();
                if (lexer.token() == Token.TABLE) {
                    lexer.nextToken();
                }
            } else if (lexer.identifierEquals(FnvHash.Constants.OVERWRITE)) {
                lexer.nextToken();
                stmt.setOverwrite(true);
                if (lexer.token() == Token.TABLE) {
                    lexer.nextToken();
                } else if (lexer.token() == Token.INTO) {
                    lexer.nextToken();
                }
            }
            if (lexer.token() == Token.LINE_COMMENT) {
                lexer.nextToken();
            }
            if (lexer.token() == Token.FULLTEXT) {
                lexer.nextToken();
                if (lexer.identifierEquals(FnvHash.Constants.DICTIONARY)) {
                    lexer.nextToken();
                    stmt.setFulltextDictionary(true);
                }
            }
            tableName = this.exprParser.name();
            stmt.setTableName(tableName);
            if (lexer.token() == Token.HINT) {
                String comment = "/*" + lexer.stringVal() + "*/";
                lexer.nextToken();
                stmt.getTableSource().addAfterComment(comment);
            }
            if (lexer.token() == Token.IDENTIFIER
                    && !lexer.identifierEquals(FnvHash.Constants.VALUE)) {
                stmt.setAlias(lexer.stringVal());
                lexer.nextToken();
            }
            if (lexer.token() == Token.WITH) {
                SQLSelectStatement withStmt = (SQLSelectStatement) parseWith();
                stmt.setQuery(withStmt.getSelect());
            }
            if (lexer.token() == Token.PARTITION) {
                lexer.nextToken();
                accept(Token.LPAREN);
                for (; ; ) {
                    SQLAssignItem ptExpr = new SQLAssignItem();
                    ptExpr.setTarget(this.exprParser.name());
                    if (lexer.token() == Token.EQ) {
                        lexer.nextToken();
                        SQLExpr ptValue = this.exprParser.expr();
                        ptExpr.setValue(ptValue);
                    }
                    stmt.addPartition(ptExpr);
                    if (lexer.token() != Token.COMMA) {
                        break;
                    } else {
                        lexer.nextToken();
                    }
                }
                accept(Token.RPAREN);
                if (lexer.token() == Token.IF) {
                    lexer.nextToken();
                    accept(Token.NOT);
                    accept(Token.EXISTS);

                    stmt.setIfNotExists(true);
                }
            }
        }
        int columnSize = 0;
        List<SQLColumnDefinition> columnDefinitionList = null;
        if (lexer.token() == Token.LPAREN) {
            boolean useInsertColumnsCache = lexer.isEnabled(SQLParserFeature.UseInsertColumnsCache);
            InsertColumnsCache insertColumnsCache = null;
            long tableNameHash = 0;
            InsertColumnsCache.Entry cachedColumns = null;
            if (useInsertColumnsCache) {
                insertColumnsCache = this.insertColumnsCache;
                if (insertColumnsCache == null) {
                    insertColumnsCache = InsertColumnsCache.global;
                }
                if (tableName != null) {
                    tableNameHash = tableName.nameHashCode64();
                    cachedColumns = insertColumnsCache.get(tableNameHash);
                }
            }
            SchemaObject tableObject = null;
            int pos = lexer.pos();
            if (cachedColumns != null
                    && lexer.text.startsWith(cachedColumns.columnsString, pos)) {
                if (!lexer.isEnabled(SQLParserFeature.OptimizedForParameterized)) {
                    List<SQLExpr> columns = stmt.getColumns();
                    List<SQLExpr> cachedColumns2 = cachedColumns.columns;
                    for (int i = 0, size = cachedColumns2.size(); i < size; i++) {
                        columns.add(cachedColumns2.get(i).clone());
                    }
                }
                stmt.setColumnsString(cachedColumns.columnsFormattedString, cachedColumns.columnsFormattedStringHash);
                int p2 = pos + cachedColumns.columnsString.length();
                lexer.reset(p2);
                lexer.nextToken();
            } else {
                Lexer.SavePoint mark = lexer.mark();
                lexer.nextToken();
                if (lexer.token() == Token.SELECT) {
                    lexer.reset(mark);
                    SQLSelect select = this.exprParser.createSelectParser().select();
                    select.setParent(stmt);
                    stmt.setQuery(select);
                } else {
                    if (repository != null && lexer.isEnabled(SQLParserFeature.InsertValueCheckType)) {
                        tableObject = repository.findTable(tableName.nameHashCode64());
                    }
                    if (tableObject != null) {
                        columnDefinitionList = new ArrayList<SQLColumnDefinition>();
                    }
                    List<SQLExpr> columns = stmt.getColumns();
                    if (lexer.token() != Token.RPAREN) {
                        for (; ; ) {
                            String identName;
                            long hash;
                            Token token = lexer.token();
                            if (token == Token.IDENTIFIER) {
                                identName = lexer.stringVal();
                                hash = lexer.hash_lower();
                            } else if (token == Token.LITERAL_CHARS) {
                                if (lexer.isEnabled(SQLParserFeature.IgnoreNameQuotes)) {
                                    identName = lexer.stringVal();
                                } else {
                                    identName = '\'' + lexer.stringVal() + '\'';
                                }
                                hash = 0;
                            } else if (token == Token.LITERAL_ALIAS) {
                                identName = lexer.stringVal();
                                if (lexer.isEnabled(SQLParserFeature.IgnoreNameQuotes)) {
                                    identName = SQLUtils.normalize(identName, dbType);
                                }
                                hash = 0;
                            } else {
                                identName = lexer.stringVal();
                                hash = 0;
                            }
                            lexer.nextTokenComma();
                            SQLExpr expr = new SQLIdentifierExpr(identName, hash);
                            while (lexer.token() == Token.DOT) {
                                lexer.nextToken();
                                String propertyName = lexer.stringVal();
                                lexer.nextToken();
                                expr = new SQLPropertyExpr(expr, propertyName);
                            }
                            expr.setParent(stmt);
                            columns.add(expr);
                            columnSize++;
                            if (tableObject != null) {
                                SQLColumnDefinition columnDefinition = tableObject.findColumn(hash);
                                columnDefinitionList.add(columnDefinition);
                            }
                            if (lexer.token() == Token.COMMA) {
                                lexer.nextTokenIdent();
                                continue;
                            }
                            break;
                        }
                        columnSize = stmt.getColumns().size();
                        if (insertColumnsCache != null && tableName != null) {
                            String columnsString = lexer.subString(pos, lexer.pos() - pos);
                            List<SQLExpr> clonedColumns = new ArrayList<SQLExpr>(columnSize);
                            for (int i = 0; i < columns.size(); i++) {
                                clonedColumns.add(columns.get(i).clone());
                            }
                            StringBuilder buf = new StringBuilder();
                            SQLASTOutputVisitor outputVisitor = SQLUtils.createOutputVisitor(buf, dbType);
                            outputVisitor.printInsertColumns(columns);
                            String formattedColumnsString = buf.toString();
                            long columnsFormattedStringHash = FnvHash.fnv1a_64_lower(formattedColumnsString);
                            insertColumnsCache.put(tableName.hashCode64(), columnsString, formattedColumnsString, clonedColumns);
                            stmt.setColumnsString(formattedColumnsString, columnsFormattedStringHash);
                        }
                    }
                    accept(Token.RPAREN);
                }
            }
        }
        List<SQLCommentHint> commentHints = null;
        if (lexer.token() == Token.HINT) {
            commentHints = this.exprParser.parseHints();
        } else if (lexer.token() == Token.LINE_COMMENT) {
            lexer.nextToken();
        }
        if (lexer.token() == Token.VALUES || lexer.identifierEquals(FnvHash.Constants.VALUE)) {
            lexer.nextTokenLParen();
            if (lexer.isEnabled(SQLParserFeature.InsertReader)) {
                return stmt;
            }
            if (lexer.isEnabled(SQLParserFeature.InsertValueNative)) {
                parseValueClauseNative(stmt.getValuesList(), columnDefinitionList, columnSize, stmt);
            } else {
                parseValueClause(stmt.getValuesList(), columnDefinitionList, columnSize, stmt);
            }
        } else if (lexer.token() == Token.SET) {
            lexer.nextToken();
            SQLInsertStatement.ValuesClause values = new SQLInsertStatement.ValuesClause();
            stmt.addValueCause(values);
            for (; ; ) {
                SQLName name = this.exprParser.name();
                stmt.addColumn(name);
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                } else {
                    accept(Token.COLONEQ);
                }
                values.addValue(this.exprParser.expr());
                if (lexer.token() == Token.COMMA) {
                    lexer.nextToken();
                    continue;
                }
                break;
            }
        } else if (lexer.token() == (Token.SELECT)) {
            SQLSelect select = this.exprParser.createSelectParser().select();
            if (commentHints != null && !commentHints.isEmpty()) {
                select.setHeadHint(commentHints.get(0));
            }
            select.setParent(stmt);
            stmt.setQuery(select);
        } else if (lexer.token() == (Token.LPAREN)) {
            lexer.nextToken();
            SQLSelect select = this.exprParser.createSelectParser().select();
            select.setParent(stmt);
            stmt.setQuery(select);
            accept(Token.RPAREN);
        } else if (lexer.token() == Token.WITH) {
            SQLSelect query = this.exprParser.createSelectParser().select();
            stmt.setQuery(query);
        }
        if (lexer.token() == Token.ON) {
            lexer.nextToken();
            acceptIdentifier("DUPLICATE");
            accept(Token.KEY);
            accept(Token.UPDATE);
            List<SQLExpr> duplicateKeyUpdate = stmt.getDuplicateKeyUpdate();
            for (; ; ) {
                SQLName name = this.exprParser.name();
                accept(Token.EQ);
                SQLExpr value;
                try {
                    value = this.exprParser.expr();
                } catch (EOFParserException e) {
                    throw new ParserException("EOF, " + name + "=", e);
                }
                SQLBinaryOpExpr assignment = new SQLBinaryOpExpr(name, SQLBinaryOperator.Equality, value);
                assignment.setParent(stmt);
                duplicateKeyUpdate.add(assignment);
                if (lexer.token() == Token.COMMA) {
                    lexer.nextTokenIdent();
                    continue;
                }
                break;
            }
        }
        return stmt;
    }

    public MySqlSelectParser createSQLSelectParser() {
        return new MySqlSelectParser(this.exprParser, selectListCache);
    }

    public SQLStatement parseSet() {
        accept(Token.SET);
        if (lexer.identifierEquals(FnvHash.Constants.PASSWORD)) {
            lexer.nextToken();
            SQLSetStatement stmt = new SQLSetStatement();
            stmt.setDbType(dbType);
            stmt.setOption(SQLSetStatement.Option.PASSWORD);
            SQLExpr user = null;
            if (lexer.token() == Token.FOR) {
                lexer.nextToken();
                user = this.exprParser.name();
            }
            accept(Token.EQ);
            SQLExpr password = this.exprParser.expr();
            stmt.set(user, password);
            return stmt;
        }

        Boolean global = null;
        Boolean session = null;
        boolean local = false;
        if (lexer.identifierEquals(GLOBAL)) {
            global = Boolean.TRUE;
            lexer.nextToken();
        } else if (lexer.identifierEquals(FnvHash.Constants.SESSION)) {
            session = Boolean.TRUE;
            lexer.nextToken();
        } else if (lexer.identifierEquals(FnvHash.Constants.LOCAL)) {
            lexer.nextToken();
            local = true;
        }
        if (lexer.identifierEquals(FnvHash.Constants.TRANSACTION)) {
            MySqlSetTransactionStatement stmt = new MySqlSetTransactionStatement();
            stmt.setGlobal(global);
            stmt.setSession(session);
            if (local) {
                stmt.setLocal(true);
            }
            lexer.nextToken();
            if (lexer.identifierEquals("ISOLATION")) {
                lexer.nextToken();
                acceptIdentifier("LEVEL");
                if (lexer.identifierEquals(READ)) {
                    lexer.nextToken();
                    if (lexer.identifierEquals("UNCOMMITTED")) {
                        stmt.setIsolationLevel("READ UNCOMMITTED");
                        lexer.nextToken();
                    } else if (lexer.identifierEquals(WRITE)) {
                        stmt.setIsolationLevel("READ WRITE");
                        lexer.nextToken();
                    } else if (lexer.identifierEquals("ONLY")) {
                        stmt.setIsolationLevel("READ ONLY");
                        lexer.nextToken();
                    } else if (lexer.identifierEquals("COMMITTED")) {
                        stmt.setIsolationLevel("READ COMMITTED");
                        lexer.nextToken();
                    } else {
                        throw new ParserException("UNKOWN TRANSACTION LEVEL : " + lexer.stringVal() + ", " + lexer.info());
                    }
                } else if (lexer.identifierEquals("SERIALIZABLE")) {
                    stmt.setIsolationLevel("SERIALIZABLE");
                    lexer.nextToken();
                } else if (lexer.identifierEquals("REPEATABLE")) {
                    lexer.nextToken();
                    if (lexer.identifierEquals(READ)) {
                        stmt.setIsolationLevel("REPEATABLE READ");
                        lexer.nextToken();
                    } else {
                        throw new ParserException("UNKOWN TRANSACTION LEVEL : " + lexer.stringVal() + ", " + lexer.info());
                    }
                } else {
                    throw new ParserException("UNKOWN TRANSACTION LEVEL : " + lexer.stringVal() + ", " + lexer.info());
                }
            } else if (lexer.identifierEquals(FnvHash.Constants.POLICY)) {
                lexer.nextToken();
                SQLExpr policy = this.exprParser.primary();
                stmt.setPolicy(policy);
            } else if (lexer.identifierEquals(READ)) {
                lexer.nextToken();
                if (lexer.identifierEquals("ONLY")) {
                    stmt.setAccessModel("ONLY");
                    lexer.nextToken();
                } else if (lexer.identifierEquals("WRITE")) {
                    stmt.setAccessModel("WRITE");
                    lexer.nextToken();
                } else {
                    throw new ParserException("UNKOWN ACCESS MODEL : " + lexer.stringVal() + ", " + lexer.info());
                }
            }
            return stmt;
        } else {
            SQLSetStatement stmt = new SQLSetStatement(getDbType());
            parseAssignItems(stmt.getItems(), stmt, true);
            if (global != null) {
                SQLVariantRefExpr varRef = (SQLVariantRefExpr) stmt.getItems().get(0).getTarget();
                varRef.setGlobal(true);
            }
            if (session != null) {
                SQLVariantRefExpr varRef = (SQLVariantRefExpr) stmt.getItems().get(0).getTarget();
                varRef.setSession(true);
            }
            if (lexer.token() == Token.HINT) {
                stmt.setHints(this.exprParser.parseHints());
            }
            return stmt;
        }
    }

    private SQLStatement parseAddManageInstanceGroup() {
        lexer.nextToken();
        MySqlManageInstanceGroupStatement stmt = new MySqlManageInstanceGroupStatement();
        stmt.setOperation(new SQLIdentifierExpr("ADD"));
        acceptIdentifier("INSTANCE_GROUP");
        for (; ; ) {
            stmt.getGroupNames().add(exprParser.expr());
            if (lexer.token() == Token.COMMA) {
                lexer.nextToken();
                continue;
            }
            break;
        }
        acceptIdentifier("REPLICATION");
        accept(Token.EQ);
        stmt.setReplication(exprParser.integerExpr());
        return stmt;
    }

    protected void parseUpdateSet(SQLUpdateStatement update) {
        accept(Token.SET);
        for (; ; ) {
            SQLUpdateSetItem item = this.exprParser.parseUpdateSetItem();
            update.addItem(item);
            if (lexer.token() != Token.COMMA) {
                break;
            }
            lexer.nextToken();
        }
    }

    @Override
    public MySqlExprParser getExprParser() {
        return (MySqlExprParser) exprParser;
    }

    public MySqlChecksumTableStatement parseChecksum() {
        MySqlChecksumTableStatement stmt = new MySqlChecksumTableStatement();
        if (lexer.identifierEquals(FnvHash.Constants.CHECKSUM)) {
            lexer.nextToken();
        } else {
            throw new ParserException("TODO " + lexer.info());
        }
        accept(Token.TABLE);
        for (; ; ) {
            SQLName table = this.exprParser.name();
            stmt.addTable(new SQLExprTableSource(table));

            if (lexer.token() == Token.COMMA) {
                lexer.nextToken();
                continue;
            }
            break;
        }
        return stmt;
    }

    /**
     * parse if statement
     *
     * @return MySqlIfStatement
     */
    public SQLIfStatement parseIf() {
        accept(Token.IF);
        SQLIfStatement stmt = new SQLIfStatement();
        stmt.setCondition(this.exprParser.expr());
        accept(Token.THEN);
        this.parseStatementList(stmt.getStatements(), -1, stmt);
        while (lexer.token() == Token.ELSE) {
            lexer.nextToken();
            if (lexer.token() == Token.IF) {
                lexer.nextToken();
                SQLIfStatement.ElseIf elseIf = new SQLIfStatement.ElseIf();
                elseIf.setCondition(this.exprParser.expr());
                elseIf.setParent(stmt);
                accept(Token.THEN);
                this.parseStatementList(elseIf.getStatements(), -1, elseIf);
                stmt.getElseIfList().add(elseIf);
            } else {
                SQLIfStatement.Else elseItem = new SQLIfStatement.Else();
                this.parseStatementList(elseItem.getStatements(), -1, elseItem);
                stmt.setElseItem(elseItem);
                break;
            }
        }
        accept(Token.END);
        accept(Token.IF);
        accept(Token.SEMI);
        stmt.setAfterSemi(true);
        return stmt;
    }

    /**
     * parse while statement
     *
     * @return MySqlWhileStatement
     */
    public SQLWhileStatement parseWhile() {
        accept(Token.WHILE);
        SQLWhileStatement stmt = new SQLWhileStatement();
        stmt.setCondition(this.exprParser.expr());
        accept(Token.DO);
        this.parseStatementList(stmt.getStatements(), -1, stmt);
        accept(Token.END);
        accept(Token.WHILE);
        accept(Token.SEMI);
        stmt.setAfterSemi(true);
        return stmt;
    }

    /**
     * parse while statement with label
     *
     * @return MySqlWhileStatement
     */
    public SQLWhileStatement parseWhile(String label) {
        accept(Token.WHILE);
        SQLWhileStatement stmt = new SQLWhileStatement();
        stmt.setLabelName(label);
        stmt.setCondition(this.exprParser.expr());
        accept(Token.DO);
        this.parseStatementList(stmt.getStatements(), -1, stmt);
        accept(Token.END);
        accept(Token.WHILE);
        acceptIdentifier(label);
        accept(Token.SEMI);
        stmt.setAfterSemi(true);
        return stmt;
    }

    /**
     * parse case statement
     *
     * @return MySqlCaseStatement
     */
    public MySqlCaseStatement parseCase() {
        MySqlCaseStatement stmt = new MySqlCaseStatement();
        accept(Token.CASE);
        if (lexer.token() == Token.WHEN) {
            while (lexer.token() == Token.WHEN) {
                MySqlCaseStatement.MySqlWhenStatement when = new MySqlCaseStatement.MySqlWhenStatement();
                // when expr
                when.setCondition(exprParser.expr());
                accept(Token.THEN);
                // when block
                this.parseStatementList(when.getStatements(), -1, when);
                stmt.addWhenStatement(when);
            }
            if (lexer.token() == Token.ELSE) {
                // parse else block
                SQLIfStatement.Else elseStmt = new SQLIfStatement.Else();
                this.parseStatementList(elseStmt.getStatements(), -1, elseStmt);
                stmt.setElseItem(elseStmt);
            }
        } else {
            // case expr
            stmt.setCondition(exprParser.expr());
            while (lexer.token() == Token.WHEN) {
                accept(Token.WHEN);
                MySqlCaseStatement.MySqlWhenStatement when = new MySqlCaseStatement.MySqlWhenStatement();
                // when expr
                when.setCondition(exprParser.expr());
                accept(Token.THEN);
                // when block
                this.parseStatementList(when.getStatements(), -1, when);
                stmt.addWhenStatement(when);
            }
            if (lexer.token() == Token.ELSE) {
                accept(Token.ELSE);
                // else block
                SQLIfStatement.Else elseStmt = new SQLIfStatement.Else();
                this.parseStatementList(elseStmt.getStatements(), -1, elseStmt);
                stmt.setElseItem(elseStmt);
            }
        }
        accept(Token.END);
        accept(Token.CASE);
        accept(Token.SEMI);
        return stmt;
    }

    /**
     * parse declare statement
     */
    public SQLStatement parseDeclare() {
        char markChar = lexer.current();
        int markBp = lexer.bp();
        lexer.nextToken();
        if (lexer.token() == Token.CONTINUE) {
            lexer.reset(markBp, markChar, Token.DECLARE);
            return this.parseDeclareHandler();
        }
        lexer.nextToken();
        if (lexer.token() == Token.CURSOR) {
            lexer.reset(markBp, markChar, Token.DECLARE);
            return this.parseCursorDeclare();
        } else if (lexer.identifierEquals("HANDLER")) {
            //DECLARE异常处理程序 [add by zhujun 2016-04-16]
            lexer.reset(markBp, markChar, Token.DECLARE);
            return this.parseDeclareHandler();
        } else if (lexer.token() == Token.CONDITION) {
            //DECLARE异常 [add by zhujun 2016-04-17]
            lexer.reset(markBp, markChar, Token.DECLARE);
            return this.parseDeclareCondition();
        } else {
            lexer.reset(markBp, markChar, Token.DECLARE);
        }
        MySqlDeclareStatement stmt = new MySqlDeclareStatement();
        accept(Token.DECLARE);
        // lexer.nextToken();
        for (; ; ) {
            SQLDeclareItem item = new SQLDeclareItem();
            item.setName(exprParser.name());
            stmt.addVar(item);
            if (lexer.token() == Token.COMMA) {
                lexer.nextToken();
                stmt.setAfterSemi(true);
                continue;
            } else if (lexer.token() != Token.EOF) {
                // var type
                item.setDataType(exprParser.parseDataType());

                if (lexer.token() == Token.DEFAULT) {
                    lexer.nextToken();
                    SQLExpr defaultValue = this.exprParser.primary();
                    item.setValue(defaultValue);
                }
                break;
            } else {
                throw new ParserException("TODO. " + lexer.info());
            }
        }
        return stmt;
    }

    /**
     * parse assign statement
     */
    public SQLSetStatement parseAssign() {
        accept(Token.SET);
        SQLSetStatement stmt = new SQLSetStatement(getDbType());
        parseAssignItems(stmt.getItems(), stmt);
        return stmt;
    }

    /**
     * parse loop statement with label
     */
    public SQLLoopStatement parseLoop(String label) {
        SQLLoopStatement loopStmt = new SQLLoopStatement();
        loopStmt.setLabelName(label);
        accept(Token.LOOP);
        this.parseStatementList(loopStmt.getStatements(), -1, loopStmt);
        accept(Token.END);
        accept(Token.LOOP);
        if (lexer.token() != Token.SEMI) {
            acceptIdentifier(label);
        }
        accept(Token.SEMI);
        loopStmt.setAfterSemi(true);
        return loopStmt;
    }

    /**
     * parse loop statement with label
     */
    public SQLBlockStatement parseBlock(String label) {
        SQLBlockStatement block = new SQLBlockStatement();
        block.setLabelName(label);
        accept(Token.BEGIN);
        this.parseStatementList(block.getStatementList(), -1, block);
        accept(Token.END);
        acceptIdentifier(label);
        return block;
    }

    /**
     * parse leave statement
     */
    public MySqlLeaveStatement parseLeave() {
        accept(Token.LEAVE);
        MySqlLeaveStatement leaveStmt = new MySqlLeaveStatement();
        leaveStmt.setLabelName(exprParser.name().getSimpleName());
        accept(Token.SEMI);
        return leaveStmt;
    }

    /**
     * parse repeat statement
     */
    public MySqlRepeatStatement parseRepeat() {
        MySqlRepeatStatement stmt = new MySqlRepeatStatement();
        accept(Token.REPEAT);
        parseStatementList(stmt.getStatements(), -1, stmt);
        accept(Token.UNTIL);
        stmt.setCondition(exprParser.expr());
        accept(Token.END);
        accept(Token.REPEAT);
        accept(Token.SEMI);
        stmt.setAfterSemi(true);
        return stmt;
    }

    /**
     * parse repeat statement with label
     *
     * @param label
     */
    public MySqlRepeatStatement parseRepeat(String label) {
        MySqlRepeatStatement repeatStmt = new MySqlRepeatStatement();
        repeatStmt.setLabelName(label);
        accept(Token.REPEAT);
        this.parseStatementList(repeatStmt.getStatements(), -1, repeatStmt);
        accept(Token.UNTIL);
        repeatStmt.setCondition(exprParser.expr());
        accept(Token.END);
        accept(Token.REPEAT);
        acceptIdentifier(label);
        accept(Token.SEMI);
        return repeatStmt;
    }

    /**
     * parse cursor declare statement
     */
    public MySqlCursorDeclareStatement parseCursorDeclare() {
        MySqlCursorDeclareStatement stmt = new MySqlCursorDeclareStatement();
        accept(Token.DECLARE);
        stmt.setCursorName(exprParser.name());
        accept(Token.CURSOR);
        accept(Token.FOR);
        SQLSelect select = this.createSQLSelectParser().select();
        stmt.setSelect(select);
        accept(Token.SEMI);
        return stmt;
    }

    /**
     * zhujun [455910092@qq.com]
     * parse spstatement
     */
    public SQLStatement parseSpStatement() {
        // update
        if (lexer.token() == (Token.UPDATE)) {
            return parseUpdateStatement();
        }
        // insert
        if (lexer.token() == Token.INSERT) {
            return parseInsert();
        }
        // delete
        if (lexer.token() == (Token.DELETE)) {
            return parseDeleteStatement();
        }
        // begin
        if (lexer.token() == Token.BEGIN) {
            return this.parseBlock();
        }
        // select
        if (lexer.token() == Token.LPAREN) {
            char ch = lexer.current();
            int bp = lexer.bp();
            lexer.nextToken();

            if (lexer.token() == Token.SELECT) {
                lexer.reset(bp, ch, Token.LPAREN);
                return this.parseSelect();
            } else {
                throw new ParserException("TODO. " + lexer.info());
            }
        }
        // assign statement
        if (lexer.token() == Token.SET) {
            return parseAssign();
        }
        throw new ParserException("error sp_statement. " + lexer.info());
    }

    /**
     * 定义异常处理程序
     *
     * @author zhujun [455910092@qq.com]
     * 2016-04-16
     */
    public MySqlDeclareHandlerStatement parseDeclareHandler() {
        // DECLARE handler_type HANDLER FOR condition_value[,...] sp_statement
        // handler_type 取值为 CONTINUE | EXIT | UNDO
        // condition_value 取值为 SQLWARNING | NOT FOUND | SQLEXCEPTION | SQLSTATE value(异常码 e.g 1062)
        MySqlDeclareHandlerStatement stmt = new MySqlDeclareHandlerStatement();
        accept(Token.DECLARE);
        if (lexer.token() == Token.CONTINUE) {
            stmt.setHandleType(MySqlHandlerType.CONTINUE);
        } else if (lexer.token() == Token.EXIT) {
            stmt.setHandleType(MySqlHandlerType.CONTINUE);
        } else if (lexer.token() == Token.UNDO) {
            stmt.setHandleType(MySqlHandlerType.CONTINUE);
        } else {
            throw new ParserException("unkown handle type. " + lexer.info());
        }
        lexer.nextToken();
        acceptIdentifier("HANDLER");
        accept(Token.FOR);
        for (; ; ) {
            String tokenName = lexer.stringVal();
            ConditionValue condition = new ConditionValue();
            if (tokenName.equalsIgnoreCase("NOT")) {//for 'NOT FOUND'
                lexer.nextToken();
                acceptIdentifier("FOUND");
                condition.setType(ConditionValue.ConditionType.SYSTEM);
                condition.setValue("NOT FOUND");
            } else if (tokenName.equalsIgnoreCase("SQLSTATE")) { //for SQLSTATE (SQLSTATE '10001')
                condition.setType(ConditionValue.ConditionType.SQLSTATE);
                lexer.nextToken();
                condition.setValue(exprParser.name().toString());
            } else if (lexer.identifierEquals("SQLEXCEPTION")) { //for SQLEXCEPTION
                condition.setType(ConditionValue.ConditionType.SYSTEM);
                condition.setValue(lexer.stringVal());
                lexer.nextToken();
            } else if (lexer.identifierEquals("SQLWARNING")) { //for SQLWARNING
                condition.setType(ConditionValue.ConditionType.SYSTEM);
                condition.setValue(lexer.stringVal());
                lexer.nextToken();
            } else { //for condition_name or mysql_error_code
                if (lexer.token() == Token.LITERAL_INT) {
                    condition.setType(ConditionValue.ConditionType.MYSQL_ERROR_CODE);
                    condition.setValue(lexer.integerValue().toString());
                } else {
                    condition.setType(ConditionValue.ConditionType.SELF);
                    condition.setValue(tokenName);
                }
                lexer.nextToken();
            }
            stmt.getConditionValues().add(condition);
            if (lexer.token() == Token.COMMA) {
                accept(Token.COMMA);
                continue;
            } else if (lexer.token() != Token.EOF) {
                break;
            } else {
                throw new ParserException("declare handle not eof");
            }
        }
        stmt.setSpStatement(parseSpStatement());
        if (!(stmt.getSpStatement() instanceof SQLBlockStatement)) {
            accept(Token.SEMI);
        }
        return stmt;
    }

    /**
     * zhujun [455910092@qq.com]
     * 2016-04-17
     * 定义条件
     */
    public MySqlDeclareConditionStatement parseDeclareCondition() {
        MySqlDeclareConditionStatement stmt = new MySqlDeclareConditionStatement();
        accept(Token.DECLARE);
        stmt.setConditionName(exprParser.name().toString());
        accept(Token.CONDITION);
        accept(Token.FOR);
        String tokenName = lexer.stringVal();
        ConditionValue condition = new ConditionValue();
        if (tokenName.equalsIgnoreCase("SQLSTATE")) { //for SQLSTATE (SQLSTATE '10001')
            condition.setType(ConditionValue.ConditionType.SQLSTATE);
            lexer.nextToken();
            condition.setValue(exprParser.name().toString());
        } else if (lexer.token() == Token.LITERAL_INT) {
            condition.setType(ConditionValue.ConditionType.MYSQL_ERROR_CODE);
            condition.setValue(lexer.integerValue().toString());
            lexer.nextToken();
        } else {
            throw new ParserException("declare condition grammer error. " + lexer.info());
        }
        stmt.setConditionValue(condition);
        accept(Token.SEMI);
        return stmt;
    }

    @Override
    public SQLStatement parseFlashback() {
        MySqlFlashbackStatement stmt = new MySqlFlashbackStatement();
        acceptIdentifier("FLASHBACK");
        accept(Token.TABLE);
        SQLName name = this.exprParser.name();
        stmt.setName(name);
        accept(Token.TO);
        acceptIdentifier("BEFORE");
        accept(Token.DROP);
        if (lexer.identifierEquals(FnvHash.Constants.RENAME)) {
            lexer.nextToken();
            accept(Token.TO);
            SQLName to = this.exprParser.name();
            stmt.setRenameTo(to);
        }
        return stmt;
    }

    @Override
    public SQLStatement parseCopy() {
        acceptIdentifier("COPY");
        SQLCopyFromStatement stmt = new SQLCopyFromStatement();
        SQLExpr table = this.exprParser.name();
        stmt.setTable(new SQLExprTableSource(table));
        if (lexer.token() == Token.LPAREN) {
            lexer.nextToken();
            this.exprParser.names(stmt.getColumns(), stmt);
            accept(Token.RPAREN);
        }
        if (lexer.token() == Token.PARTITION) {
            lexer.nextToken();
            this.exprParser.parseAssignItem(stmt.getPartitions(), stmt);
        }
        accept(Token.FROM);
        SQLExpr from = this.exprParser.expr();
        stmt.setFrom(from);
        if (lexer.identifierEquals(FnvHash.Constants.CREDENTIALS)) {
            lexer.nextToken();
            for (; ; ) {
                if (lexer.identifierEquals(FnvHash.Constants.ACCESS_KEY_ID)) {
                    lexer.nextToken();
                    SQLExpr accessKeyId = this.exprParser.primary();
                    stmt.setAccessKeyId(accessKeyId);
                } else if (lexer.identifierEquals(FnvHash.Constants.ACCESS_KEY_SECRET)) {
                    lexer.nextToken();
                    SQLExpr accessKeySecret = this.exprParser.primary();
                    stmt.setAccessKeySecret(accessKeySecret);
                } else {
                    break;
                }
            }
        }
        if (lexer.token() == Token.WITH) {
            lexer.nextToken();
            accept(Token.LPAREN);
            for (; ; ) {
                SQLName name = this.exprParser.name();
                if (lexer.token() == Token.EQ) {
                    lexer.nextToken();
                }
                SQLExpr value = this.exprParser.expr();
                SQLAssignItem item = new SQLAssignItem(name, value);
                item.setParent(stmt);
                stmt.getOptions().add(item);
                if (lexer.token() == Token.COMMA) {
                    lexer.nextToken();
                    continue;
                }
                break;
            }
            accept(Token.RPAREN);
        }
        return stmt;
    }
}
